On macOS, Claude Code stores credentials in the Keychain under
service "Claude Code-credentials" rather than in a file. When
~/.claude/.credentials.json is absent, shell out to:
security find-generic-password -s "Claude Code-credentials" -w
and parse the result as the same JSON schema.
~/.claude.json holds only profile/UI metadata (oauthAccount has
no token fields). expiresAt in the credentials is milliseconds.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The actual OAuth token is in ~/.claude/.credentials.json under
claudeAiOauth.accessToken, not in ~/.claude.json.
~/.claude.json holds only UI state and profile metadata (oauthAccount
has no token fields). expiresAt in the credentials file is milliseconds,
not seconds.
Discovered after testing against Claude Code 2.1.198.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Reads the host's Claude OAuth session key from ~/.claude.json at launch
and forwards it only to the egress sidecar (never to the agent), placing
a placeholder CLAUDE_CODE_OAUTH_TOKEN in the agent env so Claude Code
starts without seeing the real credential.
Mirrors the existing Codex forward_host_credentials flow (PRD 0029).
Adds claude_auth.py to extract and validate the sessionKey, a
CLAUDE_HOST_CREDENTIAL_TOKEN_REF constant in egress.py, and updates
manifest_agent.py to allow the flag for both 'codex' and 'claude'
templates. Also adds a mutual-exclusion check that rejects setting
both auth_token and forward_host_credentials together.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- test_contrib_gitea_client: remove unused Any import, fix _mock_response
to use return_value instead of lambda (unknown lambda type), narrow
HTTPError hdrs type, add type annotations to fake_urlopen helpers,
suppress protected-access for _request tests
- test_bootstrap: annotate **kw as **kw: object, use dict literal,
unpack server_address via index to avoid tuple type mismatch
- test_main: remove unused MagicMock import
- test_watchdog: guard store.get() result before accessing .status
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Moves the orchestrator into bot_bottle/orchestrator/ so one install gets
everything. Entry point is now `python -m bot_bottle.orchestrator run`.
- Add bot_bottle/orchestrator/ with all 14 modules (verbatim move; internal
imports were already relative, so no changes inside orchestrator modules)
- Rewrite bootstrap.py: remove the lazy bot_bottle import guard, use direct
relative imports from ..contrib.*
- Add bot_bottle/contrib/forge/base.py: ScopedForge (read-anywhere / write-scoped)
- Add bot_bottle/contrib/gitea/client.py: GiteaClient + GiteaForge (urllib.request only)
- Add bot_bottle/contrib/gitea/forge_state.py: ForgeState + SqliteForgeStateStore
- Add tests/unit/orchestrator/ (82 tests: 63 migrated + 19 new for contrib modules)
Closes#321