Propagate user-mode setup failures via exit code across all tools#188
Conversation
The user-mode setup.py installers (run interactively as `python3 <(curl ... setup.py) --domain ...`) had the same exit-code defect the MDM scripts did: main() used bare `return` (-> None) on every error path and __main__ called main() then exited 0 unless an exception was raised. So a failed install (missing key, callback failure, env-var write, hook setup, tool config) reported exit 0 to any non-interactive caller (CI, `&&` chains, wrappers). An attentive human still sees the printed ❌ and the absent "Setup Complete!", but the process exit code lied. Mirrors the MDM exit-code contract: - main() returns True on success / --clear, False on every error path - __main__ captures the result and exits 1 on KeyboardInterrupt, on exception, or when main() is falsy; 0 only on real success The check_enterprise_hooks_conflict() path keeps its explicit `raise SystemExit(3)` (deliberate distinct code for "machine is MDM-managed"); it bypasses the guard and is untouched. Also added the missing `import sys` to claude-code/gateway, codex/gateway, and gemini-cli/gateway (their guards now call sys.exit). Applied to augment, claude-code (gateway + hooks), codex (gateway + hooks), copilot, cursor, gemini-cli, and openclaw. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
vigneshsubbiah16
left a comment
There was a problem hiding this comment.
🛡️ Automated Security Review (consensus)
✅ Security consensus: no issues found. (reviewers: Cursor, Claude, Semgrep, Gitleaks)
🤖 consensus review · reviewers: Cursor,Claude,Semgrep,Gitleaks · head f5e6dad9 · 2026-06-29T09:02Z
Follow-up to PR #188 review (Greptile P1s + finder pass). The exit-code contract was over-reporting success in several spots where a step's own failure signal was ignored: - claude-code/gateway, gemini-cli/gateway: the base-URL env write (ANTHROPIC_BASE_URL / GOOGLE_GEMINI_BASE_URL) result was captured but never checked, so main() reached `return True` even when the tool was left pointed at the wrong endpoint. Now returns False on failure, matching the API-key check directly above. (Greptile P1) - openclaw + all other tools: clear_setup() tracked `any_failed` internally but returned None, while main()'s --clear branch returned True unconditionally — a partial clear reported exit 0. clear_setup() now returns `not any_failed` and main() propagates it via `return clear_setup()`. Applied uniformly to all 9 scripts (Greptile flagged openclaw; the same bug was systemic). (Greptile P1 + finder) - codex/hooks: enable_codex_hooks_feature() returns bool but its result was ignored; a failed feature-flag write left the hooks inert while setup exited 0. Now returns False on failure. (finder) Not changed: setup_claude_key_helper() and write_unbound_config() remain best-effort (void / warn-only by design) — noted on the PR. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review addressed — pushed in e325ec4All three Greptile P1s are fixed (replied inline). A focused review pass on the same risk class (operations whose failure is ignored but now reach
Deliberately left as best-effort (not failures):
Verification: |
vigneshsubbiah16
left a comment
There was a problem hiding this comment.
🛡️ Automated Security Review (consensus)
✅ Security consensus: no issues found. (reviewers: Cursor, Claude, Semgrep, Gitleaks)
🤖 consensus review · reviewers: Cursor,Claude,Semgrep,Gitleaks · head e325ec43 · 2026-06-29T09:21Z
Review follow-up (Greptile P1 "Unix Failures Still Succeed"): the gateway scripts' exit-code guards (`if not success: return False`) could not catch Unix write failures because set_env_var_on_unix() returned True unconditionally — `append_to_file()` returns False for BOTH "already present" and "write failed", and the helper did `if was_added: return True else: return True`, swallowing real failures. Fix: after the append attempt, verify the export line is actually present in the rc file. Returns True when added or already present, False when the write failed (line absent) or the file is unreadable. No regression on the idempotent already-present case. Applied to the 3 Pattern-A scripts (claude-code/gateway, codex/gateway, gemini-cli/gateway); the 6 hooks scripts already propagate failure correctly via `return append_to_file(...)`. Verified with a temp-HOME sandbox: a read-only rc file now yields False (was True); a writable rc file yields True with the line present. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
vigneshsubbiah16
left a comment
There was a problem hiding this comment.
✅ Security consensus: no issues found. (reviewers: Cursor, Claude, Semgrep, Gitleaks)
🤖 consensus review · reviewers: Cursor,Claude,Semgrep,Gitleaks · head 93c44210 · 2026-06-29T09:33Z
Self-review of PR #188 surfaced two issues: - set_env_var_on_unix() read-back caught only OSError, but rc_file.read_text(encoding="utf-8") raises UnicodeDecodeError (a ValueError) on a non-UTF-8 shell rc file — escaping the `-> bool` contract and bubbling a generic traceback to __main__ instead of a clean `return False`. Broadened to `except Exception`. (3 gateway scripts) - claude-code/gateway setup_claude_key_helper() was `-> None` and swallowed all errors with a warning, while main() called it unchecked and returned True. It writes ~/.claude/anthropic_key.sh + the apiKeyHelper setting — the gateway auth mechanism — so a failed write left Claude Code unable to authenticate while setup exited 0. Now returns bool and main() returns False on failure, consistent with how the MDM script treats its managed-settings write. Verified with temp-HOME sandbox: non-UTF-8 rc file -> set_env_var_on_unix returns False (no exception); key helper returns True on success / False when the write fails. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Self-review (3-angle pass) — 2 issues found & fixed in 2c95425Ran a line-by-line / removed-behavior / cross-file review over the full diff. Verified-safe: Fixed:
Noted, intentionally not changed (best-effort by design): Verified with a temp-HOME sandbox: non-UTF-8 rc → |
|
✅ Security consensus: no issues found. (reviewers: Cursor, Claude, Semgrep, Gitleaks) 🤖 consensus review · reviewers: Cursor,Claude,Semgrep,Gitleaks · head |
🛡️ Automated Security Review (consensus)0 findings — 0 high-confidence, 0 to triage. Reviewers: Cursor, Claude, Semgrep, Gitleaks. ✅ Security consensus: no issues found. (reviewers: Cursor, Claude, Semgrep, Gitleaks) Previously acknowledged (not re-flagged)
🤖 consensus review · reviewers: Cursor,Claude,Semgrep,Gitleaks · head |
Greptile P1 "Commented Export Succeeds": the read-back `export_line in rc_file.read_text()` did a substring match, so a disabled line like `# export ANTHROPIC_BASE_URL="..."` satisfied it — set_env_var could report success (exit 0) without an active export, leaving the tool on the old/default endpoint. Now require an exact, stripped, uncommented line match: `any(line.strip() == export_line for line in ...splitlines())`. A `# export VAR=...` line no longer counts; the real appended export still does. Keeps the broad `except Exception: return False` (non-UTF-8 rc). Applied to all 3 gateway scripts (Greptile flagged claude-code + gemini; codex/gateway has the identical read-back). Verified in a temp-HOME sandbox: commented-only rc -> False, active export -> True, non-UTF-8 rc -> False (no exception). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
vigneshsubbiah16
left a comment
There was a problem hiding this comment.
✅ Security consensus: no issues found. (reviewers: Cursor, Claude, Semgrep, Gitleaks)
🤖 consensus review · reviewers: Cursor,Claude,Semgrep,Gitleaks · head b2aa2248 · 2026-07-01T15:20Z
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 6f6aa58. Configure here.
| try: | ||
| return any(line.strip() == export_line for line in rc_file.read_text(encoding="utf-8").splitlines()) | ||
| except Exception: | ||
| return False |
There was a problem hiding this comment.
Commented export blocks gateway setup
Medium Severity
On Unix, gateway setup now verifies an exact active export line in the shell rc file, but append_to_file still skips writing when that text appears anywhere in the file (including inside a commented # export … line). With only a commented export present, no active line is added, read-back fails, and setup exits with failure even though the installer never writes a usable export.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit 6f6aa58. Configure here.
🛡️ Automated Security Review (consensus)0 findings — 0 high-confidence, 0 to triage. Reviewers: Cursor, Claude, Semgrep, Gitleaks. ✅ Security consensus: no issues found. (reviewers: Cursor, Claude, Semgrep, Gitleaks) Previously acknowledged (not re-flagged)
🤖 consensus review · reviewers: Cursor,Claude,Semgrep,Gitleaks · head |
…/user-mode-setup-exit-codes
…ebsentry-ai/setup into fix/user-mode-setup-exit-codes
vigneshsubbiah16
left a comment
There was a problem hiding this comment.
🛡️ Automated Security Review (consensus)
0 findings — 0 high-confidence, 0 to triage. Reviewers: Cursor, Claude, Semgrep, Gitleaks.
✅ Security consensus: no issues found. (reviewers: Cursor, Claude, Semgrep, Gitleaks)
Previously acknowledged (not re-flagged)
write_unbound_config()/run_backfill()warn-only paths — Maintainer: best-effort by design; hard-failing would change their contracts and is out of scope for this exit-code PR.restart_cursor()(subprocess.Popen(..., shell=True)) and openclaw npm plugin install — Maintainer: cosmetic / non-fatal best-effort; Semgrepshell=Truehit is pre-existing code on an intentionally non-blocking path.- Gateway
append_to_filelacksvar_namededup (duplicateexportlines on URL change) — Maintainer: pre-existing behavior, accepted; shell uses the last export. - Commented
# export …in rc file causesappend_to_fileto skip write → setup exits 1 — Fails closed (no false “enrolled” signal); correctness/availability nuisance, not a security exposure. Greptile read-back issues and related P1s were fixed in e325ec4 / 93c4421 / b2aa224.
🤖 consensus review · reviewers: Cursor,Claude,Semgrep,Gitleaks · head a3b7a7ee · 2026-07-03T06:21Z


What
Applies the same exit-code contract from the MDM fix (#171) to the user-mode
setup.pyinstallers — the ones a normal user runs interactively via the README/web-UI command:Files (9):
augment/hooks,claude-code/{gateway,hooks},codex/{gateway,hooks},copilot/hooks,cursor,gemini-cli/gateway,openclaw.Why
Same silent-failure class as MDM had:
main()used barereturn(→None) on every error path, and__main__calledmain()then exited 0 unless an exception was raised. So a failed install — missing--domain/--api-key, callback failure, env-var write failure, hook setup failure, tool-config failure — reported exit 0.Blast radius is smaller than MDM (an attentive human sees the printed
❌and the missingSetup Complete!banner), but the wrong exit code still lies to any non-interactive caller: CI,&&chains, wrappers, automation.Change (uniform across all 9)
main()returnsTrueon success /--clear,Falseon every error path.__main__captures the result andsys.exit(1)onKeyboardInterrupt, on exception, or whenmain()is falsy;sys.exit(0)only on real success.Notes
check_enterprise_hooks_conflict()keeps its explicitraise SystemExit(3)— a deliberate, distinct exit code for "this machine is MDM-managed, defer to MDM." It's aBaseException, so it bypasses theexcept Exceptionguard and the process still exits 3 (not conflated with success=0 or generic failure=1). Untouched.import systoclaude-code/gateway,codex/gateway, andgemini-cli/gateway— their rewritten guards callsys.exit, and these three did not previously importsys.Verification
python3 -m py_compilepasses on all 9.returnremain in anymain(); every guard ends withsys.exit(0 if ok else 1).return Falseis a real error path; no success path mis-flagged.--clearwipes live config; copilot callsinstall_macos_certificates()), so they are intentionally not executed e2e here — validated statically + by diff review. The MDM PR (Fix silent Copilot MDM hook-install failure #171) carries the executed sandbox e2e for the equivalent contract.Companion to #171 (MDM scripts).
🤖 Generated with Claude Code
Note
Low Risk
Behavioral change is limited to process exit codes and stricter success checks on local setup scripts; no auth or server logic changes, though CI/scripts that assumed exit 0 on failure may now see exit 1.
Overview
User-mode setup.py installers (nine tools) now report real success/failure to the shell instead of often exiting 0 after a failed run.
main()returnsTrueon success andFalseon every error path (missing args, auth callback failure, env/config/hook steps).clear_setup()returnsnot any_failedso--clearcan fail non-zero too.__main__usessys.exit(0 if ok else 1);KeyboardInterruptand uncaught exceptions exit 1. MDM skip still usesSystemExit(3)unchanged.Gateway installers (Claude, Codex, Gemini) add
import sysand change Unix shell export success to require an exact matchingexportline in the rc file (not merely “append attempted”). Claude gateway also fails setup ifANTHROPIC_BASE_URLorsetup_claude_key_helper()fails. Codex hooks now fails whenenable_codex_hooks_feature()fails instead of continuing.Reviewed by Cursor Bugbot for commit a3b7a7e. Bugbot is set up for automated code reviews on this repo. Configure here.
Greptile Summary
This PR makes the user-mode setup scripts report installer failures through process exit codes. The main changes are:
main()returns explicit success or failure values across the setup scripts.--clearcleanup results now flow into the final exit code.Confidence Score: 5/5
This looks safe to merge.
Important Files Changed
Reviews (8): Last reviewed commit: "Merge branch 'fix/user-mode-setup-exit-c..." | Re-trigger Greptile