feat(oauth): persist CLAUDE_CODE_OAUTH_TOKEN across macOS reboots#277
Open
Ballestar wants to merge 3 commits into
Open
feat(oauth): persist CLAUDE_CODE_OAUTH_TOKEN across macOS reboots#277Ballestar wants to merge 3 commits into
Ballestar wants to merge 3 commits into
Conversation
Add a per-user LaunchAgent that runs `launchctl setenv CLAUDE_CODE_OAUTH_TOKEN <value>` at login, so firstmate-launched agents inherit the token across reboots without a manual re-export. Chose the LaunchAgent + launchctl setenv approach over env-passthrough in a daemon plist: firstmate runs interactively in a terminal/tmux session, not as a launchd-managed daemon, so there is no existing firstmate plist to inject EnvironmentVariables into. launchctl setenv at login matches the actual process tree (GUI terminal -> login shell -> tmux server -> crewmate agents) without re-architecting how firstmate launches. The token is never committed. The plist template (bin/launchd/com.firstmate.oauth-token.plist) only references the helper by placeholder; bin/fm-oauth-token-load.sh reads the token at run time from a gitignored secure source (a 0600 file, a config/oauth-token-source cmd:/op: reference, or FM_OAUTH_TOKEN_FILE). bin/fm-oauth-token-install.sh renders and loads the plist idempotently and supports --uninstall/--status. docs/oauth-token.md covers setup, rotation, security notes, and the design choice; docs/configuration.md, docs/scripts.md, README.md, and CONTRIBUTING.md cross-reference it. tests/fm-oauth-token.test.sh covers helper modes, source resolution, error paths, plist shape, and installer idempotency hermetically (stubbed launchctl/tmux).
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What Changed
bin/fm-oauth-token-install.shandbin/fm-oauth-token-load.shplus acom.firstmate.oauth-token.plistlaunchd agent soCLAUDE_CODE_OAUTH_TOKENis loaded from a configured source (file, env, or 1Passwordop://reference) and persists across macOS reboots.op readusing the fullop://scheme to avoid colliding with a bareop:prefix, and warn on insecure token-file permissions.docs/oauth-token.md, register the new scripts indocs/scripts.md, note the env-var overrides indocs/configuration.md, and cover the behavior withtests/fm-oauth-token.test.sh.Risk Assessment
✅ Low: Well-bounded, macOS-scoped, thoroughly tested additive change; the one prior error (op:// scheme collision) is fixed and now covered by an assertion, and no new material issues surfaced.
Testing
Completed 1 recorded test check.
Pipeline
Updates from git push no-mistakes
⏭️ **intent** - skipped
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 2 issues found → auto-fixed ✅
bin/fm-oauth-token-load.sh:88- Theop:source path strips the descriptor'sop:prefix (ref=${src#op:}), but docs/oauth-token.md tells users to write the full 1Password referenceop://Vault/Item/field. Strippingop:fromop://Private/Item/fieldyields//Private/Item/field, andop read "//Private/Item/field"fails becauseop readrequires theop://scheme. Theop:marker convention collides with 1Password's ownop://URI scheme, so the documented op-source setup is broken. The test (test_op_source_uses_op_cli) doesn't catch it because itsopstub ignores the reference argument and returns a fixed value. Fix: detectop://and pass the whole descriptor toop readunchanged (only strip a bareop:prefix when it isn't alreadyop://).bin/fm-oauth-token-load.sh:68- The token-file permission check usesstat -f '%Lp'(BSD) with astat -c '%a'(GNU) fallback. This is correct on macOS (the only supported platform, gated by launchctl) but on Linux CIstat -fmeans --file-system and produces spurious output/exit behavior, so the perms warning path can misbehave there. Not user-facing on the supported platform; noting only because the test suite exercises this path.🔧 Fix: fix op:// scheme collision in oauth token source resolution
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
command -v tmux >/dev/null || { echo "tmux is required for e2e tests" >&2; exit 1; }; tmux -V; rc=0; for t in tests/*.test.sh; do echo "== $t =="; bash "$t" || rc=1; done; exit "$rc"✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.