From af0e870e0748c70d460fc048fbe739bd49930ab0 Mon Sep 17 00:00:00 2001 From: mecattaf Date: Tue, 22 Sep 2026 17:10:00 +0200 Subject: [PATCH] seats: cc and cc2 launchers start outside $HOME and set their own config dir; drop the dead claude-credentials seed Two Claude Code seats live on the coordinator: cc in ~/.claude (the personal Claude Max login) and cc2 in ~/.claude-work (the leger.run login). Tom re-logged both by hand on 2026-09-22; this makes the arrangement durable. - fish: cc/cac/cc2/cac2 become one launcher. Claude Code never saves workspace trust for $HOME (docs: "trust acceptance is held for the current session only and is not written to disk"), so a seat typed in ~ showed "Accessing workspace" on every launch; the launcher now moves into $CLAUDE_ENVELOPE (default ~/today) when started from ~. `cc` clears CLAUDE_CONFIG_DIR explicitly: a terminal opened from a cc2 session inherits it, and the old alias then silently started cc2 (measured with a stub claude under runtime-test). TALLY_SEAT names the seat to hooks and receipts. cc3 lines are untouched. - secrets: remove the claude-credentials seed and its age file. The token had been dead since the 2026-08-04 rotation, and a seed that fires whenever ~/.claude/.credentials.json is absent could only revert a fresh /login after a rebuild. Verified: nix eval of the coordinator's age.secrets no longer lists it and the other nineteen are intact. - AGENTS.md: the seats paragraph (which login is which directory, the launch rule, where the meters are). Not a rebuild, not a merge; both are Tom's. Co-Authored-By: Claude Fable 5.1 --- AGENTS.md | 13 +++++++++ home/dot_config/fish/config.fish | 44 ++++++++++++++++++++++++++----- modules/secrets.nix | 35 +++++------------------- secrets.nix | 10 ++----- secrets/claude-credentials.age | Bin 819 -> 0 bytes 5 files changed, 58 insertions(+), 44 deletions(-) delete mode 100644 secrets/claude-credentials.age diff --git a/AGENTS.md b/AGENTS.md index 3686dc7fe..4f14a3b2f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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/.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 diff --git a/home/dot_config/fish/config.fish b/home/dot_config/fish/config.fish index 86fcedec8..fa02067c4 100644 --- a/home/dot_config/fish/config.fish +++ b/home/dot_config/fish/config.fish @@ -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' diff --git a/modules/secrets.nix b/modules/secrets.nix index 20e6ede9f..6ae762bca 100644 --- a/modules/secrets.nix +++ b/modules/secrets.nix @@ -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 diff --git a/secrets.nix b/secrets.nix index 03f43d3aa..e9d5102fe 100644 --- a/secrets.nix +++ b/secrets.nix @@ -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 diff --git a/secrets/claude-credentials.age b/secrets/claude-credentials.age deleted file mode 100644 index 8d0a443d1f05539f6ed678fd6dfb2b1a41f4eb47..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 819 zcmV-31I+wkXJsvAZewzJaCB*JZZ2Jb4NK! zZg^ujLNzaOcQRsXcP~L~>+MGipItPHr-FZf`bWHd{RQEpl_O?FjuSxtImQ!-a&S7l9A3N0-yAToL}NJLm#b4+1GYIJ!kL0WDx zG*DAYM{a33GE7xQLN;Y>Yi~<%LvS!O3M~}2(FDnxh87!7ony?UC7Y1Oguk=GQP z9`f|;zUJ{788B`!ZfbhrulOYyXNCKS5bB1X6XH$CMnUMGfPB^uzQT%a6$p4Ttaj#T zI|P%9{w(FEtr2CVc#*07ik*MxU~`(v7Hn61qC-lVLS&CGzv{VANT5H_XX|fc!>|)v;xSu+ zx9v|73UjlO*X|Bp5ckty6p?CXeGMN|nY`0YEVLAwy0G-5#mMq>+PxA~MI{3VMieF; ztS-+5HXlE=CNjdqDop9>YX=D5lkZzYuIrGFug)}4vnOa~8s3}+QsSz5Y3}yN+IR+C zrEM;O;t$Ug5c^Brf~>13c}lChB0VGU+Yr@ xrE=mJWyjX}C8+3eoTFIWP6-<zD#kH&tAY#;yt