feat(install): defer app detection to first run; keep postinstall thin - #538
Conversation
Before, scripts/postinstall.js was not thin: on every npm install it loaded dist/lib/config.js, detected the packaged Codex desktop app (LOCALAPPDATA/WindowsApps/Applications probes), auto-bound runtime rotation (spawning the app router and rewriting config.toml), and installed OS launcher shortcuts. Now it only does CI/ignored-scripts and non-TTY detection (exit 0 silently) and otherwise prints a short two-line install notice to stderr; no detection, no dist imports, no filesystem mutation, always exit 0. The deferred work moved to a new lazy first-run hook, lib/runtime/first-run.ts (ensureFirstRunSetup), invoked from runCodexMultiAuthCli ahead of command dispatch: - marker file first-run-setup.json under the multi-auth runtime root (~/.codex/multi-auth), claimed with an exclusive wx create so concurrent first invocations run setup at most once, then finalized via the repo's temp+rename atomic write with withFileOperationRetry (Windows-safe) - gates preserved from postinstall: CI/ignored-scripts guard wins over all opt-ins; CODEX_MULTI_AUTH_APP_BIND / _APP_BIND_INSTALL / _APP_LAUNCHER_INSTALL / _RUNTIME_ROTATION_PROXY overrides honored; app bind additionally requires the desktop app to be detected or already bound. The npm_config_global gate (meaningless at runtime) is replaced by an installed-package-context check (path contains a node_modules segment), so dev checkouts and the test suite stay side-effect-free - never blocks or breaks a command: every step is try/caught, failures resolve to skipped/failed statuses with debug-only logging of error messages (no tokens/emails), and the call site adds a belt-and-braces .catch Tests: postinstall suite rewritten for the thin behavior (CI silent exit 0, non-TTY silent, TTY notice-only, throwing sink still returns 0, no detection/dist/fs imports); new test/first-run.test.ts covers detection + gates, runs-once marker creation, skip on second run, at-most-once under concurrent invocations, setup failure not failing the command, CI skip without marker, non-installed-context skip, and CLI wiring. Docs (README, CONFIG_FIELDS) updated from "during install/update" to "on first CLI run"; uninstall help and preuninstall comments updated to stop claiming postinstall does the bind/launcher work. Note: postinstall was already CI-aware and best-effort, but not thin — the detection/bind/launcher work genuinely ran at install time and is what moved. Pre-existing (also on clean origin/main): 2 Windows shortcut-routing assertions in test/install-codex-auth.test.ts fail on Linux. Audit roadmap §4.5.4. https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
|
Warning Review limit reached
More reviews will be available in 6 minutes and 2 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (10)
✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify code
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…oader Review follow-ups (P1s + P2): - isInstalledPackageContext now approximates the old npm_config_global gate at runtime: npx cache runs (_npx) and project-local installs (module under the invoking cwd) are excluded, so trying the tool via npx cannot mutate ~/.codex or burn the once-only marker before a real global install - loadLauncherInstall gains a test-only candidate override and unit coverage for the fallback order, import failure, and missing-export cases - test cleanup uses removeWithRetry per test/AGENTS.md instead of bare rmSync https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB
Repo governance requires upgrade notes for behavior changes; the postinstall-to-first-run move qualifies. Documents the marker path, the npx/project-local skip, the unchanged opt-outs, and the expected first-command outcome. https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB
Summary
Makes install lazy — audit roadmap §4.5.4 (
docs/audits/AUDIT_2026-06-10.md, PR #522): the heavy app-bind/launcher detection moves out ofpostinstallinto a once-only, never-throws first-run hook in the CLI, andpostinstallbecomes a thin CI-aware notice. This removes a whole class of install-time fragility (spawning the router, rewritingconfig.toml, scanningLOCALAPPDATA//Applicationsduringnpm install).Before / after
scripts/postinstall.jsdist/lib/config.js, desktop-app detection, app bind (router spawn + config.toml rewrite + startup entries), launcher shortcut installnode:fs, always exit 0 (invariants test-enforced)ensureFirstRunSetup()inlib/runtime/first-run.ts, called fromrunCodexMultiAuthClibefore dispatchFirst-run hook design
~/.codex/multi-auth/first-run-setup.json: claimed with exclusivewxcreate (cross-process at-most-once), finalized via the repo's temp+rename atomic pattern insidewithFileOperationRetry(Windows-safe).completed/skipped/failedstatuses; even marker-claim failure resolves; belt-and-braces.catchat the call site. Debug-only logging, no tokens/emails.CODEX_MULTI_AUTH_APP_BIND/_APP_BIND_INSTALL/_APP_LAUNCHER_INSTALL/_RUNTIME_ROTATION_PROXYhonored; bind still requires a detected or already-bound desktop app. The install-context check became "module path contains node_modules", keeping dev checkouts and tests side-effect-free.Tests + docs
test/first-run.test.ts(new, 15 tests): gates, once-only + marker contents, second-run skip, concurrency at-most-once, step-failure-doesn't-fail, CI skip, dev-checkout skip, CLI wiring.Validation
npm run typecheck+typecheck:scripts; eslint--max-warnings=0install-codex-authfailures are environment-only — independently re-verified byte-identical on clean origin/mainCI=1postinstall silent exit 0; TTY notice-only; dist-module first runcompleted→already-done; failure injection resolves without throwing; real-defaults run produced correct marker JSONRisk / Rollback
Behavioral change to install flow (deliberate, per the audit): consumers get the bind/launcher setup on first
codex-multi-authinvocation instead of duringnpm install. Revert the single commit to restore eager postinstall.https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB
Generated by Claude Code
note: greptile review for oc-chatgpt-multi-auth. cite files like
lib/foo.ts:123. confirm regression tests + windows concurrency/token redaction coverage.Greptile Summary
moves desktop-app detection, Codex app bind, and launcher routing out of
npm postinstallinto a once-only first-run hook (lib/runtime/first-run.ts) called before CLI dispatch;postinstallbecomes a thin, always-exit-0 TTY notice with no dist imports or filesystem mutations.lib/runtime/first-run.tsclaims a per-user marker (~/.codex/multi-auth/first-run-setup.json) with an exclusivewriteFileSync(flag:"wx")for cross-process at-most-once semantics, then finalizes it atomically viawithFileOperationRetry+rename; every step is best-effort and failure only debug-logs sanitized messages.isInstalledPackageContextapproximates the oldisGlobalNpmInstallgate at CLI runtime using module path heuristics: requires anode_modulessegment, excludes_npxcache runs, and excludes paths whoserelative(cwd, modulePath)resolves without a..prefix; there is an edge-case false-negative for pnpm/yarn global installs when cwd is an ancestor of the globalnode_moduleslocation (e.g., running from~/).loadLauncherInstallfallback/failure modes, and CLI wiring; usesremoveWithRetrypertest/AGENTS.md; missing a coverage case for the "partial marker" state (claim written, process killed before finalize).Confidence Score: 5/5
safe to merge; the change is a deliberate behavioral shift from eager postinstall to lazy first-run, and every failure path resolves gracefully without blocking any CLI command
the core once-only guarantee is backed by OS-level exclusive file creation (wx flag) and the atomic temp+rename finalize; the belt-and-braces .catch at the call site means no first-run failure can ever surface to the user; the postinstall simplification removes an entire class of install-time fragility
lib/runtime/first-run.ts — the isInstalledPackageContext CWD-relative heuristic has a false-negative for pnpm/yarn global installs when the user's working directory is an ancestor of the global node_modules path
Important Files Changed
Sequence Diagram
sequenceDiagram participant CLI as runCodexMultiAuthCli participant FRS as ensureFirstRunSetup participant FS as Filesystem participant Bind as defaultBindCodexApp participant Launch as defaultInstallLauncher CLI->>FRS: "await ensureFirstRunSetup({ notify }).catch(()=>undefined)" FRS->>FRS: isCiEnvironment(env)? alt CI / ignored-scripts FRS-->>CLI: "{ran:false, reason:"ci"}" else not installed package context FRS->>FRS: isInstalledPackageContext() FRS-->>CLI: "{ran:false, reason:"not-installed"}" else marker already exists FRS->>FS: existsSync(markerPath) FS-->>FRS: true FRS-->>CLI: "{ran:false, reason:"already-done"}" else first run FRS->>FS: "writeFileSync(markerPath, claim, {flag:"wx"})" Note over FS: exclusive create — at most one winner cross-process FRS->>Bind: bindCodexApp() Bind-->>FRS: "completed" | "skipped" | throws→"failed" FRS->>Launch: installLauncher() Launch-->>FRS: "completed" | "skipped" | throws→"failed" FRS->>FS: atomicWriteMarker (temp+rename via withFileOperationRetry) FRS-->>CLI: "{ran:true, appBind, launcher}" end CLI->>CLI: continue normal dispatchPrompt To Fix All With AI
Reviews (3): Last reviewed commit: "docs(upgrade): note the first-run setup ..." | Re-trigger Greptile