chenhg5/cc-connect · error
claudeSession: run_as_user spawn refused: %w
Error message
claudeSession: run_as_user spawn refused: %w
What it means
newClaudeSession (agent/claudecode/session.go:347) refuses to spawn Claude Code under run_as_user isolation until the privilege-drop path is proven viable. It runs core.VerifyRunAsUserCheap with a 10-second timeout using a sudo runner; any verification failure (sudo unavailable, wrong password config, target user missing, timeout) is returned as 'claudeSession: run_as_user spawn refused: %w' and the session is cancelled. This is a deliberate pre-flight guard, not a runtime crash.
Source
Thrown at agent/claudecode/session.go:347
// outerArgs are understood by both the wrapper and Claude CLI directly.
var outerArgs []string
if model != "" {
outerArgs = append(outerArgs, "--model", model)
}
slog.Debug("claudeSession: starting", "innerArgs", core.RedactArgs(innerArgs), "outerArgs", core.RedactArgs(outerArgs), "dir", workDir, "mode", mode, "run_as_user", spawnOpts.RunAsUser)
// Per-spawn defense in depth: if run_as_user is set, re-run the cheap
// preflight (sudo still works + target still can't escalate) right
// before we build the command. This catches sudoers being edited
// between startup preflight and now.
if spawnOpts.IsolationMode() {
verifyCtx, verifyCancel := context.WithTimeout(sessionCtx, 10*time.Second)
err := core.VerifyRunAsUserCheap(verifyCtx, core.ExecSudoRunner{}, spawnOpts.RunAsUser)
verifyCancel()
if err != nil {
cancel()
return nil, fmt.Errorf("claudeSession: run_as_user spawn refused: %w", err)
}
}
// Build final argument list.
// When cmdArgsFlag is set (e.g. "-a"), inner args are bundled into a
// single passthrough string via that flag, while outer args (--model etc.)
// are appended directly so the wrapper can also interpret them.
// Args containing spaces/newlines are quoted so the wrapper's command-line
// parser (e.g. splitCommandLine) keeps them as single tokens.
// Result: my-cli code -t foo -a "--verbose --append-system-prompt 'long text'" --model x
var allArgs []string
if cmdArgsFlag != "" {
allArgs = append(allArgs, cliExtraArgs...)
allArgs = append(allArgs, cmdArgsFlag, shellJoinArgs(innerArgs))
allArgs = append(allArgs, outerArgs...)
} else {
allArgs = append(allArgs, cliExtraArgs...)
allArgs = append(allArgs, innerArgs...)View on GitHub (pinned to 4000b2338a)
Solutions
- Inspect the wrapped cause: 'sudo: command not found' → install sudo; 'a password is required' → add a NOPASSWD sudoers rule.
- Add sudoers entry: <cc-connect-user> ALL=(<run-as-user>) NOPASSWD: ALL, then test with sudo -u <run-as-user> true -u as the service user.
- Verify the target account exists and is not locked/expired (passwd -S <user>, chage -l <user>).
- If isolation isn't needed, remove run_as_user from the claudecode agent config so the plain spawn path is used.
- If sudo is just slow, fix PAM/NSS latency (e.g. remove network lookups) so verification fits in 10s.
Example fix
// before (/etc/sudoers.d/cc-connect — missing) // cc-connect cannot sudo to the run-as user → spawn refused // after # /etc/sudoers.d/cc-connect (chmod 0440) ccbot ALL=(alice) NOPASSWD: /usr/bin/claude, /bin/kill # validate: sudo visudo -c && sudo -u ccbot sudo -u alice true
Defensive patterns
Strategy: validation
Validate before calling
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := core.VerifyRunAsUserCheap(ctx, core.ExecSudoRunner{}, targetUser); err != nil {
return fmt.Errorf("run_as_user %s not usable yet: %w", targetUser, err)
} Try / catch
sess, err := agent.StartSession(ctx, opts)
if err != nil && strings.Contains(err.Error(), "run_as_user spawn refused") {
return nil, fmt.Errorf("isolation preflight failed; verify sudoers grants for %q before starting sessions: %w", opts.RunAsUser, err)
} Prevention
- Install a NOPASSWD sudoers rule for the cc-connect user → target run_as_user, and validate with sudo -u tests.
- Ensure sudo exists inside containers/images used for deployment.
- Confirm the target account is unlocked and unexpired (passwd -S, chage -l).
- Run the cheap verify at service startup (not per-session) to surface misconfig early.
- Remove run_as_user from config if isolation is not actually required.
When it happens
Trigger: Calling StartSession when spawnOpts.IsolationMode() is true (run_as_user configured) and VerifyRunAsUserCheap fails within 10s: sudo binary missing, sudoers not permitting NOPASSWD for the cc-connect user, target run_as_user account doesn't exist or is locked, or sudo prompts for a password (blocking until timeout).
Common situations: Deploying with run_as_user set but forgetting the sudoers entry ('ccbot ALL=(target) NOPASSWD: ...'); running cc-connect in a container without sudo installed; target user created with an expired/locked account; slow sudo/PAM making the 10s verify timeout fire.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- passwordless sudo to user %q failed (check that your sudoers
- VerifyRunAsUserCheap: runAsUser is empty
- RunIsolationProbe: RunAsUser is empty
- PreflightRunAsUser: RunAsUser is empty
- project %q: target user %q can run passwordless sudo. The ru
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/43ba004c24c3cbab.
Report an issue: GitHub.