Skip to content

v3.1: per-account dir + workspace pin (no more keychain materialize)#13

Merged
gradyzhuo merged 45 commits into
mainfrom
worktree-v3.1-experimental
May 28, 2026
Merged

v3.1: per-account dir + workspace pin (no more keychain materialize)#13
gradyzhuo merged 45 commits into
mainfrom
worktree-v3.1-experimental

Conversation

@gradyzhuo
Copy link
Copy Markdown
Member

Summary

v3.1 is a structural rework of how orrery manages Claude credentials. The v3.0.x design — swap keychain items inside a shared CLAUDE_CONFIG_DIR — caused identity/plan drift (the gradyzhuo+team / jiabao+max mixed-display bug), /login poisoning of pool slots, and mid-session /status flapping when another shell ran orrery use. v3.1 fixes the root cause: each Claude account gets its own CLAUDE_CONFIG_DIR, with workspace content (projects/, memory/, agents/, commands/, todos/) shared across pinned accounts via symlinks.

New conceptual model:

  • Account = identity (oauthAccount, userID, credential) — lives in ~/.orrery/accounts/claude/<id>/
  • Workspace = work context (sessions, memory, projects) — lives in ~/.orrery/envs/<name>/claude-workspace/
  • Pin = which workspace an Account symlinks into (default: `origin`)

orrery use X now just export CLAUDE_CONFIG_DIR=<X's dir> in the shell — no keychain copy, no .claude.json mutation, no disturbance to running claude sessions. The new `claude()` shell wrapper handles per-launch identity/shared merge and per-exit split.

Delivered across 4 plans (43 commits)

  • Plan 1 (foundation, 16 commits): `Account.workspace` field, `ClaudeAccountDirectory` (per-account dir + 5 symlinks), `ClaudeJsonMerge` (per-account vs shared field categorization), `orrery pin` command
  • Plan 2 (launch hooks, 7 commits): `_prepare-claude-launch` and `_capture-claude-exit` internal subcommands, `claude()` shell wrapper that merges identity+shared into `.claude.json` at launch and splits back at exit
  • Plan 3 (migration + use UX + rename, 8 commits): opt-in `orrery migrate-to-v3.1`, `_account-dir` internal lookup, shell `use)` case that exports CLAUDE_CONFIG_DIR, `orrery workspace` alias for `orrery sandbox`, phantom loop integration
  • Plan 4 (cleanup, 12 commits): auto-migrate on startup, deleted `KeychainCredentialAdapter` + `ClaudeOAuthSnapshot` + `ToolAuth.liveActiveInfo`, RunCommand claude branches gone, `orrery sandbox` removed from public CLI, L10n vocabulary rename, AccountAddFinalize auto-applies v3.1 layout

Net: +~2300 / -~900 lines, 315 tests passing (244 baseline + ~70 new).

Out of scope (deferred)

  • Origin's existing `/.claude/projects/` session content is NOT migrated into the new workspace dir — needs separate UX design around the existing `/.claude` symlink
  • The `--sandbox`/`-s` ArgumentParser flag in set-env/unset-env remains as-is (renaming would be a breaking arg-parser change)
  • `SandboxCommand` type stays internal (referenced by `WorkspaceCommand`'s subcommand alias); a future PR can inline it

Design docs

  • `docs/superpowers/specs/2026-05-27-v3.1-account-workspace-pin-design.html`
  • `docs/superpowers/specs/2026-05-27-claude-launch-prepare-design.html`
  • `docs/superpowers/plans/2026-05-27-v3.1-foundation.{md,html}`
  • `docs/superpowers/plans/2026-05-28-v3.1-shell-launch-hooks.md`
  • `docs/superpowers/plans/2026-05-28-v3.1-migration-and-use-ux.md`
  • `docs/superpowers/plans/2026-05-28-v3.1-cleanup.md`

Test Plan

  • `swift build` succeeds
  • `swift test` — 315/315 pass
  • Manual smoke (Plan 1 T12): `orrery pin` creates account dir + workspace + 5 symlinks correctly
  • Manual smoke (Plan 2 T7): real claude through wrapper writes identity to per-account, shared to workspace, zero cross-pollution (9/9 verify)
  • Manual smoke (Plan 3 T8): migrated/un-migrated mix — v3.1 accounts export CLAUDE_CONFIG_DIR, legacy fall through to materialize, real claude resume works
  • Manual smoke (Plan 4 T11): fresh install → auto-migrate fires → `orrery use` exports → real claude through wrapper → 9/9 verify; `orrery sandbox` removed from `--help`; `orrery workspace` works
  • CI runs on push (Release workflow builds for macOS arm64 + Linux x86_64/arm64)
  • Side-by-side install (`bash /tmp/orrery-v3.1-install.sh install`) — verify against your real claude accounts without touching production v3.0.4

🤖 Generated with Claude Code

gradyzhuo and others added 30 commits May 27, 2026 15:54
…gin)

Code-review follow-up on T1: use the `workspace:` init parameter rather
than mutating after construction, and assert the raw JSON contains
`"workspace":"work"` so a future typo in `CodingKeys` would surface
instead of being masked by the `?? "origin"` decode fallback. Plus a
new test that explicit "origin" round-trips distinctly from the
absent-field default fallback.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…links

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Replace precondition(account.tool == .claude) with guarded throw
  of Error.wrongTool — matches KeychainCredentialAdapter convention,
  surfaces a recoverable error instead of crashing.
- Distinguish "wrong symlink" (replace) from "real directory at
  symlink path" (throw Error.existingDirectoryAtSymlinkPath) — the
  latter could contain user / claude data; silently deleting it
  would be a data-loss footgun on a repoint.
- Add 3 tests covering the repoint path, the real-dir-collision
  guard, and the wrong-tool guard.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
….json

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…stderr

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…yout

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…counts

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
gradyzhuo and others added 15 commits May 28, 2026 13:57
…s via use), migrate surfaces errors

- use): short-circuit -h/--help/--version before _account-dir command
  substitution; otherwise ArgumentParser's stdout help text gets exported
  as a garbage CLAUDE_CONFIG_DIR
- phantom loop: change `command orrery-bin use` to `orrery use` so the
  shell function's use) case updates CLAUDE_CONFIG_DIR for v3.1 accounts;
  v3.0.4 sandboxes still work (use) falls through internally)
- MigrateToV31Command: let acctStore.list errors propagate rather than
  silently treating store failure as "no accounts to migrate"

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…arded)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ored Account.email/plan

No tests deleted — no tests exercised the live-read path directly.
All 320 tests pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…cBack (v3.1)

- prepareMaterialize: early-return when tool == .claude; shell function wrapper
  + per-account dir layout owns claude setup, not the binary.
- prepareSyncBack: early-return when tool == .claude; CaptureClaudeExitCommand
  via claude-identity.json owns exit capture, not RunCommand.
- Add test: claudePrepareMaterializeIsNoOp (RunCommandTests)
- Add test: claudePrepareSyncBackIsNoOp (AccountInfoTests)
- No claude-specific tests deleted: existing tests already used .codex only.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ell wrapper in v3.1

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…e use

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… claude errors surface

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Remove L10n.Sandbox.* keys entirely from l10n-signatures.json
- Add L10n.Workspace.set-env/unset-env keys (11 strings total in Workspace namespace)
- Update en.json, zh-Hant.json, ja.json: replace sandbox.* strings with
  workspace.* equivalents using workspace vocabulary throughout
- Update workspace.abstract in all locales to remove sandbox references
- Update SandboxCommand.swift: all L10n.Sandbox.* → L10n.Workspace.*
- Update keys.md: remove ## sandbox section, expand ## workspace section

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…stSandboxHeader vocabulary

- AccountAddFinalizeCommand calls ClaudeAccountMigration.migrateAccount
  for new claude accounts so they get the v3.1 per-account-dir layout
  immediately — closes the "new account after first auto-migrate" gap
  where the .v3.1-account-layout-migrated flag would silently skip the
  newly-added account
- account.listSandboxHeader value changes from "sandbox: {name}" to
  "workspace: {name}" in en/zh-Hant/ja — last user-visible string still
  using sandbox vocabulary after Plan 4 T10's rename pass

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The Plan 1 foundation md + html were written to the worktree dir earlier
(visible in main repo as untracked since they sit at the worktree root path
which both git working trees see). These 3 are the remaining ones written
for Plans 2/3/4 and used as the source-of-truth task lists during execution.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ace flag alias

1. Extract SandboxCommand.subcommandTypes as a public static let so both
   SandboxCommand and WorkspaceCommand reference it explicitly. Avoids
   WorkspaceCommand silently breaking if SandboxCommand.configuration's
   shape ever changes.
2. Add --workspace/-w as flag aliases for --sandbox/-s in set-env and
   unset-env subcommands. Property renamed sandbox -> workspace for
   internal consistency with the v3.1 vocabulary. Both old and new
   flag spellings continue to work (no breaking change).
@gradyzhuo gradyzhuo merged commit c334fd8 into main May 28, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant