Skip to content

fix(kimi-code): exit 1 when a headless (-p) turn fails#1483

Merged
wbxl2000 merged 1 commit into
mainfrom
fix/headless-exit-code
Jul 7, 2026
Merged

fix(kimi-code): exit 1 when a headless (-p) turn fails#1483
wbxl2000 merged 1 commit into
mainfrom
fix/headless-exit-code

Conversation

@wbxl2000

@wbxl2000 wbxl2000 commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

Related Issue

No linked issue. Found while investigating two failed benchmark runs of kimi -p that exited cleanly but scored 0.

Problem

Headless (kimi -p) runs could exit with code 0 when a turn fails, instead of code 1. To a caller (e.g. the benchmark harness) this looks like a successful run, so the failure goes unnoticed.

Root cause. When a turn fails, the error has to traverse an async chain to reach the process.exit(1) call:

runPromptTurn reject → runPrompt finally { await cleanupPromptRun() }
                     → main .catch() → await logStartupFailure() → process.exit(1)

Node only keeps the process alive while the event loop has a ref'd handle; a pending promise does not count. The cleanup's awaits (notably telemetry's unref'd retry backoff when the network is blocked) can leave the loop with no ref'd handle, so Node drains and exits with the default code 0 before process.exit(1) ever runs.

There are two distinct drain points:

  • Scenario BrunPrompt's finally { await cleanupPromptRun() } drains. The error never propagates out of the finally, so main's .catch() never runs.
  • Scenario C — cleanup completes and the error reaches .catch(), but .catch()'s await logStartupFailure(...) drains before process.exit(1).

This was nondeterministic: it depended on whether a ref'd handle happened to survive until the chain completed (e.g. whether the telemetry endpoint was reachable).

What changed

  • src/cli/run-prompt.ts — make raceWithTimeout's cleanup timeout ref'd. This keeps the event loop alive during cleanup (bounded by timeoutMs), so Scenario B can't drain mid-cleanup; the rejection propagates to main's .catch() normally.
  • src/main.ts — set process.exitCode = 1 synchronously at the start of .catch(), before any await. Covers Scenario C: even if the loop drains during await logStartupFailure(...), the drain-exit reports failure.
  • test/cli/main.test.ts — assert the failure exit code is set before awaiting startup-failure logging.

An earlier iteration also set process.exitCode = 1 in a catch inside runPrompt, but that mutated global process.exitCode for any direct caller (including the existing run-prompt.test.ts rejection cases). Since the ref'd cleanup timeout already prevents Scenario B, that belt-and-suspenders write was redundant and was removed.

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked a related issue, or explained the problem above.
  • I have added tests that prove my feature works.
  • Ran gen-changesets skill, or this PR needs no changeset.
  • Ran gen-docs skill, or this PR needs no doc update.

@changeset-bot

changeset-bot Bot commented Jul 7, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: ca7a1b4

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@moonshot-ai/kimi-code Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Jul 7, 2026

Copy link
Copy Markdown
pnpm dlx https://pkg.pr.new/@moonshot-ai/kimi-code@ca7a1b4
npx https://pkg.pr.new/@moonshot-ai/kimi-code@ca7a1b4

commit: ca7a1b4

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 64d8cf456c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread apps/kimi-code/src/cli/run-prompt.ts Outdated
// code 0. Setting the exit code here makes that drain-exit report failure
// too. main's .catch() sets the same code as a belt-and-suspenders for the
// case where cleanup completes but a later await in .catch() drains.
process.exitCode = 1;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Prevent handled runPrompt failures from leaking exitCode

When runPrompt is called directly and the caller handles the rejection, as the existing apps/kimi-code/test/cli/run-prompt.test.ts rejection cases do, this mutates the real process.exitCode even when a fake io.process is supplied and nothing restores it. That leaves the Vitest worker/process marked as failed after an asserted rejection, so the CLI test suite can exit with status 1 despite passing assertions; keep this side effect in the entrypoint or make direct callers/tests restore it.

Useful? React with 👍 / 👎.

Headless (`kimi -p`) failures could exit with code 0 when the event loop
drained during the shutdown cleanup (e.g. telemetry's unref'd retry backoff
when the network is blocked), because the rejection never reached the
process.exit(1) call. Set the failure exit code before any await in both
the run-prompt catch and the main catch, and keep the cleanup timeout ref'd
so the loop stays alive long enough for the rejection to propagate.
@wbxl2000 wbxl2000 force-pushed the fix/headless-exit-code branch from 64d8cf4 to ca7a1b4 Compare July 7, 2026 15:30
@wbxl2000

wbxl2000 commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Swish!

Reviewed commit: ca7a1b4281

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@wbxl2000 wbxl2000 merged commit f30781b into main Jul 7, 2026
11 checks passed
@wbxl2000 wbxl2000 deleted the fix/headless-exit-code branch July 7, 2026 15:38
@github-actions github-actions Bot mentioned this pull request Jul 7, 2026
sailist pushed a commit that referenced this pull request Jul 8, 2026
Headless (`kimi -p`) failures could exit with code 0 when the event loop
drained during the shutdown cleanup (e.g. telemetry's unref'd retry backoff
when the network is blocked), because the rejection never reached the
process.exit(1) call. Set the failure exit code before any await in both
the run-prompt catch and the main catch, and keep the cleanup timeout ref'd
so the loop stays alive long enough for the rejection to propagate.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant