2026-06-17 — claude-tmux: skip interactive-claude first-run onboarding¶
Problem¶
The claude-tmux provider drives the interactive Claude Code TUI (claude --model …) in a
tmux pane. In a fresh pod it stalled: the TUI showed its first-run "Select login method"
onboarding screen and waited for a keypress, ignoring CLAUDE_CODE_OAUTH_TOKEN. The pasted prompt
went nowhere, the pane stayed static, and after idleStallMs (default 360s) the provider threw
tmux session went idle … and fell back to claude-cli — ~6 min wasted per turn for zero
observability.
Captured failure pane (via the #598 stdout pane-logging):
Welcome to Claude Code v2.1.179 … Select login method:
❯ 1. Claude account with subscription
2. Anthropic Console account
3. 3rd-party
Root cause¶
Claude Code gates its first-run onboarding flow on hasCompletedOnboarding in ~/.claude.json
before consulting CLAUDE_CODE_OAUTH_TOKEN (Claude Code 2.1.x; anthropics/claude-code#28438).
Headless claude -p skips onboarding — which is why claude-cli never needed this — but the
interactive TUI runs the onboarding flow. The image baked /home/agent/.claude/settings.json but
never created /home/agent/.claude.json (a sibling of the ~/.claude/ dir, which may be a
mounted PVC), so onboarding was incomplete and the login-method picker showed. The pod runs as the
non-root agent user, so this is not a root-permissions issue.
The provider's settle loop waits for a stable, non-empty pane; the static onboarding screen satisfies that, so it "settled" and pasted into a dead picker.
Fix¶
- Seed
~/.claude.json.ensureClaudeOnboarded(cwd, home)reads the existing file (if any), mergeshasCompletedOnboarding: trueand pre-trusts the task'sworkDir(projects.<cwd>.hasTrustDialogAccepted+hasCompletedProjectOnboarding), and writes it back — idempotent and key-preserving (claude rewrites the file with its own state on exit). Called before every interactive launch, using the child'sHOME. The pure merge ismergeOnboardingConfig(unit-tested). - Bake a baseline in
Dockerfile.base:echo '{"hasCompletedOnboarding":true}' > /home/agent/.claude.json, so a fresh pod is correct even before the provider runs and for any interactiveclaudelaunched outside the provider. - Pre-trust the per-task cwd. The agent uses a fresh working directory each turn; without
pre-trust, the "Do you trust the files in this folder?" dialog (which
isPermissionPromptdoes not match) would re-prompt and stall. The seed marks each task's cwd trusted. - Fail-fast safety net.
isOnboardingScreen/isTrustPromptdetect a residual login-method or trust screen in both the settle loop and the poll loop; if either appears, the provider throws a clearly-labeled retryableAgentProviderErrorso the chain falls back toclaude-cliimmediately instead of idle-stalling foridleStallMs. The failure mode + pane tail reach pod stdout and the cockpit Terminal tab (via #594/#598), so a missed seed is diagnosable rather than silent.
Why this is safe / behaviour-neutral¶
- No credential or billing change: the TUI still authenticates from the pod's existing
CLAUDE_CODE_OAUTH_TOKEN; the fallback chain (claude-cli) is unchanged. claude-cli(headless) is unaffected — it never showed onboarding.- The seed only sets onboarding/trust markers; claude owns the rest of
~/.claude.json.
Verification¶
- Unit tests (
claude-tmux.test.js):mergeOnboardingConfig(fresh / preserve / augment / no-cwd / non-object),ensureClaudeOnboarded(fresh write + merge under a temp HOME),isOnboardingScreen,isTrustPrompt. Full suite green. - Post-deploy verification (pending): after this merges and dev-e rolls to the new image, dev-e runs
a task and the pane should reach a working session (no "Select login method"), with the previous
tmux session went idle …no longer appearing in dev-e stdout.
References¶
- Issue: dashecorp/rig-agent-runtime#600
- anthropics/claude-code#28438 — onboarding gate precedes auth-token use
- Prior diagnosability work: #594 (de-swallow fallback cause), #598 (mirror failure pane to stdout)