fix(cli): exit after hooks inspection output#76922
Conversation
|
@codex review |
|
Codex review: needs real behavior proof before merge. Reviewed July 6, 2026, 10:50 AM ET / 14:50 UTC. Summary PR surface: Source +15, Tests +40. Total +55 across 2 files. Reproducibility: yes. source-level only: current main hooks commands build plugin diagnostics and do not request a one-shot exit, so plugin/register code that leaves handles open can keep the process alive. No fresh real terminal proof on the latest PR head was supplied. Review metrics: 1 noteworthy metric.
Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Proof guidance:
Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Land the narrow hooks one-shot exit change after redacted terminal or log proof shows the lingering-plugin-handle scenario exits after output while relay/install/update remain on the normal path. Do we have a high-confidence way to reproduce the issue? Yes, source-level only: current main hooks commands build plugin diagnostics and do not request a one-shot exit, so plugin/register code that leaves handles open can keep the process alive. No fresh real terminal proof on the latest PR head was supplied. Is this the best way to solve the issue? Yes, this looks like the best narrow fix shape: reuse the shared one-shot helper after AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 5733fb0e193e. Label changesLabel justifications:
Evidence reviewedPR surface: Source +15, Tests +40. Total +55 across 2 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
Review history (3 earlier review cycles)
|
|
Codex Review: Didn't find any major issues. 🎉 ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
Synced this PR with latest main using a normal merge commit, no force push. I kept the new upstream changelog entries and kept this PRs hooks CLI exit fix. Local checks:
@codex review |
|
Codex Review: Didn't find any major issues. Nice work! ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
Synced latest main again with a normal merge commit, no force push. Only changelog moved. I kept the new upstream TUI fix note and kept this PRs hooks CLI exit note. Local checks:
@codex review |
|
@codex review |
|
Codex Review: Didn't find any major issues. 🎉 ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
Synced latest main once more with a normal merge commit, no force push. Only changelog moved again. I kept the new upstream Telegram/TUI notes and kept this PRs hooks CLI exit note. Local checks:
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d2ff905c31
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| function shouldForceExitAfterHooksInspection(env: NodeJS.ProcessEnv = process.env): boolean { | ||
| return !isVitestRuntimeEnv(env); | ||
| } |
There was a problem hiding this comment.
Avoid suppressing one-shot exit on NODE_ENV=test
shouldForceExitAfterHooksInspection now delegates to isVitestRuntimeEnv, which treats any NODE_ENV="test" process as a test runtime. That means real CLI invocations run under NODE_ENV=test (common in CI wrappers/dev shells) will skip the new forced-exit path and can still hang when a hook/plugin leaves open handles, so the fix is silently disabled in a non-test environment.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed this.
I changed the guard so NODE_ENV=test does not suppress the forced exit anymore. Only Vitest-specific env vars skip it now.
Local checks:
pnpm test src/cli/hooks-cli.test.tspnpm check:no-conflict-markerspnpm exec oxfmt --check --threads=1 CHANGELOG.md src/cli/hooks-cli.ts src/cli/hooks-cli.test.tsgit diff --check
@codex review
There was a problem hiding this comment.
Codex Review: Didn't find any major issues. 🎉
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Synced latest main with a normal merge commit, no force push. I kept the new upstream changelog notes and moved this PRs changelog line away from the constantly changing top of the fixes section. I also kept the Local checks:
@codex review |
|
Codex Review: Didn't find any major issues. 🚀 ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
Synced latest main with a normal merge commit, no force push. I kept the new upstream Telegram changelog note and kept this PRs hooks CLI exit note. The code behavior is unchanged. Local checks:
@codex review |
|
Codex Review: Didn't find any major issues. Can't wait for the next one! ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
Synced latest main after the 2026.5.4 release using a normal merge commit, no force push. I kept upstream changelog changes and re-added this PRs hooks CLI exit note. The code behavior is unchanged. Local checks:
@codex review |
|
Codex Review: Didn't find any major issues. Breezy! ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
This pull request has been automatically marked as stale due to inactivity. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e7e9505251
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| async function runOneShotHooksCliAction(action: () => Promise<void> | void): Promise<void> { | ||
| await runHooksCliAction(action); | ||
| exitAfterHooksInspection(); |
There was a problem hiding this comment.
Delay forced hooks exits until CLI cleanup finishes
When a hooks command loads plugin diagnostics, plugins can register async teardown work such as agent-harness dispose() hooks; runCli currently awaits disposeCliAgentHarnesses() and closeCliMemoryManagers() in its finally after parseAsync returns (src/cli/run-main.ts:1343-1348). Calling exitAfterHooksInspection() from inside the Commander action schedules process.exit(0) before those awaited finalizers are guaranteed to resolve, so a hooks command that loaded a plugin with an async dispose/subprocess cleanup can terminate mid-cleanup and leave plugin-owned resources behind. The hard exit should be owned after the top-level CLI cleanup has completed, while still forcing one-shot hook commands to exit afterward.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a8fa8c5452
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| async function runOneShotHooksCliAction(action: () => Promise<void> | void): Promise<void> { | ||
| await runHooksCliAction(action); | ||
| exitAfterHooksInspection(); |
There was a problem hiding this comment.
Delay forced exit until CLI finalizers complete
When these hooks commands load plugin diagnostics that register async teardown, this schedules runtime.exit(0) from inside the Commander action, before runCli reaches its finally cleanup that awaits disposeCliAgentHarnesses() and closeCliMemoryManagers(). In the case this change is meant to fix—plugin/register code leaving handles open—the new hard exit can run on the next stream-drain callback or setImmediate while those top-level finalizers are still pending, so plugin-owned subprocesses or memory managers can be terminated mid-cleanup rather than cleanly disposed.
Useful? React with 👍 / 👎.
4699212 to
479d36f
Compare
|
Addressed the Codex feedback at exact head Local verification:
@codex review |
|
Codex Review: Didn't find any major issues. Swish! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Co-authored-by: dorukardahan <35905596+dorukardahan@users.noreply.github.com>
479d36f to
845bd07
Compare
|
Land-ready maintainer pass complete on What changed:
Proof:
Known proof gaps: none for this bounded CLI lifecycle change. |
|
Thanks @steipete, really appreciate the land-ready pass. The process-level test with a real temp plugin and a referenced handle is exactly the kind of proof this needed. My unit/helper coverage was pointing in the right direction, but this is much closer to the real failure mode. i will use that pattern earlier for CLI lifecycle fixes next time. |
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
What Problem This Solves
openclaw hooksinspection and mutation commands can finish writing their output but keep the process alive indefinitely when plugin registration leaves a referenced timer, socket, or other handle behind.On current
main, a temporary plugin whoseregister()method starts a referenced interval produced completehooks list --jsonoutput, then timed out after five seconds instead of exiting.Why This Change Was Made
The bounded hooks commands now reuse the CLI's existing deferred one-shot exit lifecycle. Their actions request exit only after command work and output complete;
runCliremains responsible for proxy, harness, memory, stdin, and output-stream teardown before the process exits.The long-running
hooks relaycommand and delegated compatibilityinstall/updatecommands retain their existing lifecycle. A process-level regression test launches the real source CLI, loads a temporary plugin, leaves a referenced interval open, and verifies valid JSON plus exit code 0.User Impact
Scripts and terminals running bare
hooks,hooks list,hooks info,hooks check,hooks enable, orhooks disablenow return promptly after completing. Command syntax and output are unchanged.Evidence
mainemitted 128 bytes of valid JSON, registered the test plugin, then timed out after 5,021 ms (exit 124).tbx_01kwvy3gp0jsf8c8rgy1ep1bze(tidal-krill): 147 focused tests passed across the hooks CLI, deferred exit helper, and top-level cleanup ordering.check:changedpassed, including core and core-test tsgo, changed-file lint, import-cycle analysis, and policy guards.