{"record":{"id":"43ba004c24c3cbab","repo":"chenhg5/cc-connect","slug":"claudesession-run-as-user-spawn-refused-w","errorCode":null,"errorMessage":"claudeSession: run_as_user spawn refused: %w","messagePattern":"claudeSession: run_as_user spawn refused: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"agent/claudecode/session.go","lineNumber":347,"sourceCode":"\t// outerArgs are understood by both the wrapper and Claude CLI directly.\n\tvar outerArgs []string\n\tif model != \"\" {\n\t\touterArgs = append(outerArgs, \"--model\", model)\n\t}\n\n\tslog.Debug(\"claudeSession: starting\", \"innerArgs\", core.RedactArgs(innerArgs), \"outerArgs\", core.RedactArgs(outerArgs), \"dir\", workDir, \"mode\", mode, \"run_as_user\", spawnOpts.RunAsUser)\n\n\t// Per-spawn defense in depth: if run_as_user is set, re-run the cheap\n\t// preflight (sudo still works + target still can't escalate) right\n\t// before we build the command. This catches sudoers being edited\n\t// between startup preflight and now.\n\tif spawnOpts.IsolationMode() {\n\t\tverifyCtx, verifyCancel := context.WithTimeout(sessionCtx, 10*time.Second)\n\t\terr := core.VerifyRunAsUserCheap(verifyCtx, core.ExecSudoRunner{}, spawnOpts.RunAsUser)\n\t\tverifyCancel()\n\t\tif err != nil {\n\t\t\tcancel()\n\t\t\treturn nil, fmt.Errorf(\"claudeSession: run_as_user spawn refused: %w\", err)\n\t\t}\n\t}\n\n\t// Build final argument list.\n\t// When cmdArgsFlag is set (e.g. \"-a\"), inner args are bundled into a\n\t// single passthrough string via that flag, while outer args (--model etc.)\n\t// are appended directly so the wrapper can also interpret them.\n\t// Args containing spaces/newlines are quoted so the wrapper's command-line\n\t// parser (e.g. splitCommandLine) keeps them as single tokens.\n\t// Result: my-cli code -t foo -a \"--verbose --append-system-prompt 'long text'\" --model x\n\tvar allArgs []string\n\tif cmdArgsFlag != \"\" {\n\t\tallArgs = append(allArgs, cliExtraArgs...)\n\t\tallArgs = append(allArgs, cmdArgsFlag, shellJoinArgs(innerArgs))\n\t\tallArgs = append(allArgs, outerArgs...)\n\t} else {\n\t\tallArgs = append(allArgs, cliExtraArgs...)\n\t\tallArgs = append(allArgs, innerArgs...)","sourceCodeStart":329,"sourceCodeEnd":365,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/agent/claudecode/session.go#L329-L365","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before (/etc/sudoers.d/cc-connect — missing)\n// cc-connect cannot sudo to the run-as user → spawn refused\n// after\n# /etc/sudoers.d/cc-connect (chmod 0440)\nccbot ALL=(alice) NOPASSWD: /usr/bin/claude, /bin/kill\n# validate: sudo visudo -c && sudo -u ccbot sudo -u alice true","handlingStrategy":"validation","validationCode":"ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)\ndefer cancel()\nif err := core.VerifyRunAsUserCheap(ctx, core.ExecSudoRunner{}, targetUser); err != nil {\n    return fmt.Errorf(\"run_as_user %s not usable yet: %w\", targetUser, err)\n}","typeGuard":null,"tryCatchPattern":"sess, err := agent.StartSession(ctx, opts)\nif err != nil && strings.Contains(err.Error(), \"run_as_user spawn refused\") {\n    return nil, fmt.Errorf(\"isolation preflight failed; verify sudoers grants for %q before starting sessions: %w\", opts.RunAsUser, err)\n}","preventionTips":["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."],"tags":["claudecode","isolation","sudo","permissions"],"backgroundTag":"permission-denied","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}