Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
**Claude seats (2026-09-22).** Two Claude Code logins live on the coordinator
and nowhere else: `cc` is `~/.claude` (the personal Claude Max login, config file
`~/.claude.json`), `cc2` is `~/.claude-work` (the leger.run Claude Max login,
config file `~/.claude-work/.claude.json`), selected only by `CLAUDE_CONFIG_DIR`
in the fish launchers (`home/dot_config/fish/config.fish`). Both share the same
skills and settings links; login, history, sessions and trust are per seat, so a
session id resumes only on the seat and from the cwd that created it
(`claude-sessions` fans out over the seats). Claude never saves workspace trust
for `$HOME`, so the launchers move into `$CLAUDE_ENVELOPE` (default `~/today`)
when typed from `~`; never start a seat in the home directory. Logins are hand
`/login`s, never a delivered secret (the old claude-credentials seed was removed
this day). The seat meters are `~/.local/state/tally-rewrite/meters/<seat>.json`.

**Physical seats (2026-09-16, supersedes older headless/client-only wording below).**
Tom is returning the coordinator to primary-desktop duty with two upright LG
5K displays side by side at scale 2. Both coordinator and client have
Expand Down
44 changes: 37 additions & 7 deletions home/dot_config/fish/config.fish
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,43 @@ alias l.="eza -a | grep -E \"^\.\""
alias cd='z'
# zi command

# Claude Code
alias cc='claude --dangerously-skip-permissions'
alias cac='claude --continue --dangerously-skip-permissions'
# Second Claude account (work). CLAUDE_CONFIG_DIR isolates login, history and
# state in ~/.claude-work; skills/settings are the same dotfiles links (home.nix).
alias cc2='env CLAUDE_CONFIG_DIR=$HOME/.claude-work claude --dangerously-skip-permissions'
alias cac2='env CLAUDE_CONFIG_DIR=$HOME/.claude-work claude --continue --dangerously-skip-permissions'
# Claude Code: two seats, one launcher shape (2026-09-22).
# cc / cac -> ~/.claude the personal Claude Max login (gmail org)
# cc2 / cac2 -> ~/.claude-work the leger.run Claude Max login
# CLAUDE_CONFIG_DIR isolates login, history and state per seat; skills and
# settings are the same dotfiles links for both (home.nix). TALLY_SEAT names the
# seat to hooks and receipts. Claude Code never saves workspace trust for $HOME
# (docs, security: "trust acceptance is held for the current session only and
# is not written to disk"), so a seat started in ~ shows "Accessing workspace"
# on every launch. Both launchers therefore move into the desk envelope when
# typed from ~; the shell stays there afterwards, which is where the desk
# session's files belong (~/today/CLAUDE.md section 4).
set -q CLAUDE_ENVELOPE; or set -gx CLAUDE_ENVELOPE $HOME/today
function __claude_seat --description 'start one Claude seat outside $HOME'
set -l seat $argv[1]
set -l config_dir $argv[2]
set -l args $argv[3..]
if test (pwd) = $HOME
if not test -d $CLAUDE_ENVELOPE
echo "$seat: \$CLAUDE_ENVELOPE ($CLAUDE_ENVELOPE) does not exist; cd into a project first" >&2
return 1
end
builtin cd $CLAUDE_ENVELOPE
echo "($seat: started in $CLAUDE_ENVELOPE, not in ~)" >&2
end
# A terminal opened from inside a cc2 session already carries
# CLAUDE_CONFIG_DIR, so `cc` must clear it explicitly or it silently starts
# cc2 (measured 2026-09-22). Each seat sets its own value or none.
if test -n "$config_dir"
env CLAUDE_CONFIG_DIR=$config_dir TALLY_SEAT=$seat claude --dangerously-skip-permissions $args
else
env -u CLAUDE_CONFIG_DIR TALLY_SEAT=$seat claude --dangerously-skip-permissions $args
end
end
function cc; __claude_seat cc "" $argv; end
function cac; __claude_seat cc "" --continue $argv; end
function cc2; __claude_seat cc2 $HOME/.claude-work $argv; end
function cac2; __claude_seat cc2 $HOME/.claude-work --continue $argv; end
# Third Claude account (2026-09-05), same rotation: state in ~/.claude-3.
alias cc3='env CLAUDE_CONFIG_DIR=$HOME/.claude-3 claude --dangerously-skip-permissions'
alias cac3='env CLAUDE_CONFIG_DIR=$HOME/.claude-3 claude --continue --dangerously-skip-permissions'
Expand Down
35 changes: 6 additions & 29 deletions modules/secrets.nix
Original file line number Diff line number Diff line change
Expand Up @@ -23,35 +23,12 @@ in

config = lib.mkIf cfg.enable (
lib.mkMerge [
# Claude Code OAuth credential — coordinator ONLY, and the recipient tier in
# ../secrets.nix now matches (aug04 ruling), so this MUST stay host-gated:
# no other host can decrypt the ciphertext, and declaring an undecryptable
# secret fails activation. The zenbook was already excluded (jul12 ruling:
# the laptop is a standalone backup operator for when the coordinator is
# unreachable, so it logs in with its OWN fresh OAuth session instead of
# inheriting the coordinator's token — two devices refreshing one shared
# token can race and sign each other out); the nas joined it aug04 for the
# simpler reason that it has no `claude` and never spent the token.
(lib.mkIf (config.networking.hostName == "coordinator") {
age.secrets.claude-credentials = {
file = ../secrets/claude-credentials.age;
owner = "tom";
group = "users";
mode = "600";
};

# Seed the Claude Code OAuth credential once into a WRITABLE path Claude owns —
# agenix delivers a read-only /run/agenix symlink, but Claude must rewrite the
# file on token refresh, so copy rather than link, and only if absent.
system.userActivationScripts.seedClaudeCreds.text = ''
cred="$HOME/.claude/.credentials.json"
if [ ! -e "$cred" ] && [ -r "${config.age.secrets.claude-credentials.path}" ]; then
mkdir -p "$HOME/.claude"
cp "${config.age.secrets.claude-credentials.path}" "$cred"
chmod 600 "$cred"
fi
'';
})
# (claude-credentials — the Claude Code OAuth token seeded into
# ~/.claude/.credentials.json — was declared and copied here until
# 2026-09-22. The token had been dead since its August 4 rotation, and a
# seed that fires whenever the file is absent can only ever revert a fresh
# `/login` after a rebuild. Both Claude seats now log in by hand, once,
# on the coordinator: cc into ~/.claude, cc2 into ~/.claude-work.)

# NOT ungated any more (2026-08-28). This block delivers ssh-user-key and
# atuin-key, whose ciphertexts are encrypted to the `delivered` tier — and
Expand Down
10 changes: 2 additions & 8 deletions secrets.nix
Original file line number Diff line number Diff line change
Expand Up @@ -152,14 +152,8 @@ in
# cleanup — as agenix ciphertext it survives reflash and never needs re-minting.
"secrets/immich-api-key.age".publicKeys = editors ++ coordinatorOnly;

# Claude Code OAuth credential. Demoted from the common tier to coordinator-only
# (2026-08-04, Tom's ruling): the nas holds no `claude` binary and never ran an
# agent, so it had no use for the token; zenbook-duo was already excluded from
# delivery (jul12 ruling — it logs in with its OWN session, since two devices
# refreshing one shared token race and sign each other out). That left the
# coordinator as the only real consumer, so the recipient tier now says so —
# a token this wide should not be decryptable by boxes that never spend it.
"secrets/claude-credentials.age".publicKeys = editors ++ coordinatorOnly;
# (claude-credentials.age removed 2026-09-22: the seat logins are hand
# `/login`s on the coordinator, never a delivered secret; see modules/secrets.nix.)

# Brother HL-L2445DW Web Based Management admin password, set 2026-08-21 when
# the printer's forced default-password change gated its move onto the thomas
Expand Down
Binary file removed secrets/claude-credentials.age
Binary file not shown.