{"record":{"id":"6899c02bb7ae69b4","repo":"chenhg5/cc-connect","slug":"passwordless-sudo-to-user-q-failed-check-that-yo","errorCode":null,"errorMessage":"passwordless sudo to user %q failed (check that your sudoers rule is present and scoped to this user): %w: %s","messagePattern":"passwordless sudo to user %q failed \\(check that your sudoers rule is present and scoped to this user\\): %w: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"core/runas.go","lineNumber":244,"sourceCode":"//\n// Returns nil if both checks behave as expected. Results are cached for\n// verifyCacheTTL keyed by runAsUser so rapid-fire messages don't pay the\n// ~100ms cost per spawn. A failure evicts the cache immediately so the\n// next spawn re-verifies fresh.\n//\n// The expensive checks (work_dir access, isolation probe) live in the\n// preflight and audit packages and only run at startup / via `cc-connect\n// doctor user-isolation`.\nfunc VerifyRunAsUserCheap(ctx context.Context, runner SudoRunner, runAsUser string) error {\n\tif runAsUser == \"\" {\n\t\treturn errors.New(\"VerifyRunAsUserCheap: runAsUser is empty\")\n\t}\n\tif verifyCacheHit(runAsUser) {\n\t\treturn nil\n\t}\n\tif out, err := runner.Run(ctx, \"-n\", \"-iu\", runAsUser, \"--\", \"/usr/bin/true\"); err != nil {\n\t\tverifyCacheEvict(runAsUser)\n\t\treturn fmt.Errorf(\"passwordless sudo to user %q failed (check that your sudoers rule is present and scoped to this user): %w: %s\", runAsUser, err, strings.TrimSpace(string(out)))\n\t}\n\tout, err := runner.Run(ctx, \"-n\", \"-iu\", runAsUser, \"--\", \"sudo\", \"-n\", \"/usr/bin/true\")\n\tif err == nil {\n\t\tverifyCacheEvict(runAsUser)\n\t\treturn fmt.Errorf(\"target user %q can run passwordless sudo; isolation is meaningless. Remove NOPASSWD sudo for this user. Output: %s\", runAsUser, strings.TrimSpace(string(out)))\n\t}\n\tverifyCacheStore(runAsUser)\n\treturn nil\n}\n\n// verifyCacheTTL is short by design. It absorbs a burst of messages\n// (one Slack user typing rapidly) while still re-verifying often enough\n// that a sudoers edit during a long idle gap is caught on the next spawn.\nconst verifyCacheTTL = 30 * time.Second\n\nvar (\n\tverifyCacheMu sync.Mutex\n\tverifyCache   = map[string]time.Time{}","sourceCodeStart":226,"sourceCodeEnd":262,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/core/runas.go#L226-L262","documentation":"VerifyRunAsUserCheap (core/runas.go:244) runs 'sudo -n -iu <user> -- /usr/bin/true' to confirm passwordless sudo to the target run-as user works; failure means the sudoers rule is missing, mis-scoped, requires a password, or the target user doesn't exist. The underlying sudo error and stderr are embedded in the message.","triggerScenarios":"Called (from newClaudeSession) when starting a run-as-isolated session and the sudo probe exits non-zero: no NOPASSWD rule for the supervisor to the target user, rule limited to specific commands, target username misspelled/nonexistent, sudo asking for a password or a TTY, or the verify cache was expired and re-verified a now-broken setup.","commonSituations":"Fresh deployment where the sudoers file was never updated; sudoers rule edited to 'user ALL=(ALL) NOPASSWD' but run via a runner binary that doesn't match the rule's command scope; target user removed by provisioning changes; sudoers requires requiretty.","solutions":["Add a sudoers rule granting the supervisor user passwordless sudo to the target: <supervisor> ALL=(<target>) NOPASSWD: ALL in /etc/sudoers.d/","Verify manually: sudo -n -iu <target> -- /usr/bin/true","Confirm the target user exists (id <target>) and the username matches config exactly","Ensure the sudoers rule permits the exact invocation path (if scoped to a command, include /usr/bin/true and sudo itself); run visock/visudo -c to check syntax","Check for requiretty or sudo password policy that blocks non-interactive use"],"exampleFix":"// before: /etc/sudoers has no rule for the supervisor\n// sudo: a password is required\n// after: /etc/sudoers.d/cc-connect\nsupervisor ALL=(agentuser) NOPASSWD: ALL","handlingStrategy":"validation","validationCode":"func sudoToTargetOK(target string) error {\n\tout, err := exec.Command(\"sudo\", \"-n\", \"-iu\", target, \"--\", \"/usr/bin/true\").CombinedOutput()\n\tif err != nil { return fmt.Errorf(\"sudo to %s failed: %w: %s\", target, err, strings.TrimSpace(string(out))) }\n\treturn nil\n}\n// call before configuring run_as_user in config.toml","typeGuard":null,"tryCatchPattern":"sess, err := newClaudeSession(ctx, cfg)\nif err != nil {\n\tif strings.Contains(err.Error(), \"passwordless sudo to user\") {\n\t\tlog.Fatalf(\"Fix sudoers first: %v\\nHint: add '%s ALL=(%s) NOPASSWD: ALL' to /etc/sudoers.d/\", err, currentUsername(), cfg.RunAsUser)\n\t}\n\treturn err\n}","preventionTips":["Add the sudoers rule as a provisioning step (idempotent file in /etc/sudoers.d) before first start","Test 'sudo -n -iu <user> -- /usr/bin/true' in CI/deploy smoke tests","Ensure the rule covers the exact supervisor user the daemon runs as (systemd User=)","Run visudo -c after any sudoers change"],"tags":["sudo","permissions","isolation","runas"],"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"}