Skip to content

chore: Sandboxed claude-code jobs fail OAuth on turn 1 while the host login is valid - #893

Merged
getappz merged 2 commits into
masterfrom
task/355-sandboxed-claude-code-jobs-fail-oauth-on
Oct 7, 2026
Merged

getappz merged 2 commits into
masterfrom
task/355-sandboxed-claude-code-jobs-fail-oauth-on

Conversation

@getappz

@getappz getappz commented Oct 7, 2026 •

Copy link
Copy Markdown
Owner

Fixing the expired OAuth error for dispatched jobs — tracing the sandboxed credential path.
The overlay setup discards credential writes — now digging into the sandbox mechanics to confirm.
Sandbox mounts check out — now examining how dispatched jobs launch Claude.
Host login works — now reproducing the failure inside the sandbox.
Minimal sandbox works — now checking what env the real dispatch path adds.
Sandbox works with a valid token — now forcing the refresh path to reproduce the failure.
Unsandboxed refresh succeeds — now the same expired token inside the sandbox.
Sandboxed refresh works too — shifting to concurrent-refresh and dispatch-path theories.
Now reading the dispatch failure-classification path.
Checking whether Anthropic rotates refresh tokens — the key to the concurrent-job theory.
Reproduced the exact error. Verifying the mechanism — did the sandboxed refresh consume the shared refresh token?
Host credentials look wiped — inspecting carefully before touching anything.
Host OAuth session got wiped during these experiments — checking for recovery options and timing.
Root cause proven. Checking what depends on the current overlay policy before fixing.
Now the bwrap handling for the new policy.
Now switching the Claude mount to the persistent policy.
Adding unit tests for the new policy.
Implementation complete — running verification.
Fmt drift is pre-existing (newer toolchain) — my code matches file style. Checking whether clippy warnings are mine.
All clean — no warnings in touched files. Cleaning up and committing.
LOC gate tripped — moving my new tests into a submodule per the gate's guidance.
Status: fixed and committed (f7709de on task/355-..., 4 files).

Root cause (proven live): ~/.claude was OverlayEphemeral. Anthropic rotates both OAuth tokens on every refresh, consuming the old refresh token server-side. A sandboxed job's refresh succeeded in-job but the rotated pair was discarded on exit, leaving the host file with dead credentials. The next refresh attempt fails with exactly the reported Failed to authenticate: OAuth session expired and could not be refreshed, and claude then wipes the credentials file. Reproduced end-to-end with an expired-token copy: sandboxed refresh exited 0, replay of the same file failed and zeroed it.

Fix: new MountPolicy::Persist (writable --bind-try, symlink/escape-safe) in flare-sandbox; ~/.claude switched from overlay to persist in agentflare-jobs::sandbox. Refreshes now persist to the host and concurrent jobs share .oauth_refresh.lock, matching unsandboxed behavior. All other agents stay overlay-ephemeral (new regression test enforces this).

Tests (foreground): cargo test -p flare-sandbox --lib 55/55 pass (3 new persist tests, split into persist_mount_tests.rs to satisfy the LOC gate); cargo test -p agentflare-jobs --lib sandbox 10/10 pass (1 new); clippy clean on touched files; fmt matches file style (remaining fmt --check drift is pre-existing toolchain churn in untouched files).

Concern / action needed: my repro consumed and then wiped the box's real Claude OAuth session — ~/.claude/.credentials.json now has empty tokens (expiresAt: 0). I had no valid tokens to restore. The operator must re-login (claude login) before any claude-code dispatch will work; until then jobs will fail auth. Temp experiment files under /tmp were removed.


Opened by opencode on i-flare-51bb8de6c33b for item #355 via agentflare.

Summary by CodeRabbit

  • New Features
    • Claude Code’s OAuth credentials and refresh lock now persist across jobs. Other agent state remains temporary.
  • Bug Fixes
    • Persistent state is mounted only when its destination is a valid directory under the home directory; absent or unsafe destinations are skipped.

…355)

Agentflare-Agent: opencode
Agentflare-Branch: task/355-sandboxed-claude-code-jobs-fail-oauth-on
Agentflare-Item: 355
@coderabbitai

coderabbitai Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: getappz/agentflare/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Essentials
  • Run ID: 0db72ea2-9cdc-460b-b449-9e8bfb512de9
📥 Commits

Reviewing files that changed from the base of the PR and between 83cf426 and 9d6133d.

📒 Files selected for processing (4)
  • crates/agentflare-jobs/src/sandbox.rs
  • crates/flare-sandbox/src/bwrap/mod.rs
  • crates/flare-sandbox/src/bwrap/persist_mount_tests.rs
  • crates/flare-sandbox/src/lib.rs

Included review availability: This review used your included allowance. 2 included reviews remain after this review. Your included PR review attempts over the past 7 days set your current allowance at 5 reviews per hour.


📝 Walkthrough

Walkthrough

Claude Code’s .claude state mount now uses Persist; other agent-state mounts remain ephemeral. The sandbox binds eligible existing paths with --bind-try and skips paths that are missing or unresolvable.

Changes

Claude persistent state

Layer / File(s) Summary
Define and select persistent state
crates/flare-sandbox/src/lib.rs, crates/agentflare-jobs/src/sandbox.rs
The policy documentation describes persistent writes and OAuth state. Claude Code’s .claude mount now uses Persist. A test checks that other agent profiles remain overlay-ephemeral.
Bind persistent state paths
crates/flare-sandbox/src/bwrap/mod.rs, crates/flare-sandbox/src/bwrap/persist_mount_tests.rs
The sandbox uses --bind-try for existing resolvable paths and emits a skipped-mount event for present but unresolvable paths. Tests cover existing paths, absent paths, and live or dangling symlinks.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Merge Risk: ⚪ Minimal · up to 9d613

Claude Code sandboxed jobs now keep refreshed OAuth credentials on the host, which should stop the turn-1 authentication failures. No merge-blocking risk was identified. The operator still needs to run claude login after the credential wipe the author reported.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 10 functions across 4 files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly identifies the sandboxed Claude Code OAuth failure that this pull request fixes. It is specific and relevant to the main change.
Description check ✅ Passed The description provides the root cause, implementation details, test results, risk information, and required operator action. It does not follow the template headings or checkbox format exactly, but …
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@getappz
getappz merged commit f538a64 into master Oct 7, 2026
17 checks passed
@getappz
getappz deleted the task/355-sandboxed-claude-code-jobs-fail-oauth-on branch October 7, 2026 15:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant