Skip to content

runner: exit after the result line drains (#63) - #67

Closed
gabe-bots[bot] wants to merge 2 commits into
mainfrom
quality/63-runner-drain
Closed

gabe-bots[bot] wants to merge 2 commits into
mainfrom
quality/63-runner-drain

Conversation

@gabe-bots

@gabe-bots gabe-bots Bot commented Oct 26, 2026

Copy link
Copy Markdown
Contributor

Intent

A large run_code result reaches the host as one whole line. The host then clips that value and names recall.

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Program returns a large value] --> B[finish writes the result line]
  B --> C[Write callback waits for the pipe]
  C --> D[process.exit after the line is out]
  D --> E[takeMessages reads one done line]
  E --> F[Host clips the value and names recall]
Loading

Drift

None.

Decisions

  • The runner exits in the stdout write callback. fs.writeSync can throw EAGAIN on a pipe.
  • Door: two-way. A revert of 7f9f09b restores the old exit.

Evidence

Before (EXPECT_NETWORK_BLOCK=1 node tests/runner.integration.mjs, then the host-text script):

FAIL  make it fail: a 200000-char return arrives whole through a pipe
      exit 0 stdout 146176 rest 146176 newline false
      0 !== 1
FAIL  make it fail: a 1000000-char return arrives whole through a pipe
      exit 0 stdout 146176 rest 146176 newline false
      0 !== 1
2 failed
{"exit":0,"stdoutBytes":146176,"rest":146176,"newline":false,"done":null,"valueLen":null}
Error: the sandbox exited without a result.

--- 0 MCP calls: 0 characters in, 0 out ---

After:

pass  make it fail: a 200000-char return arrives whole through a pipe
pass  make it fail: a 1000000-char return arrives whole through a pipe
all passed
{"exit":0,"stdoutBytes":200042,"rest":0,"newline":true,"done":"done","valueLen":200000}
… [180002 more characters cut; await recall(1) returns the whole result: return a part of it]

--- 0 MCP calls: 0 characters in, 200k out ---

Reading map

  • Tests: tests/runner.integration.mjs, the two make it fail cases.
  • Config: .claude-plugin/plugin.json version 0.12.14.

Stack

Step 1 of 2. The base is main. There is no previous step. The next branch is quality/64-pretty-cut.

Part of #63

Story

A large run_code result reaches the host whole, and the host keeps each result that it cuts for recall.

# Step Evidence Branch
1 The runner exits before stdout drains 200,000 and 1,000,000-character returns arrive whole through a pipe quality/63-runner-drain
2 The cut uses the shown text An 800-object result names await recall(n), and recall(n) gives back 800 items quality/64-pretty-cut

@gabe-bots gabe-bots Bot added the needs-gabe Needs Gabe's decision before work continues label Oct 26, 2026
@gabe4coding

Copy link
Copy Markdown
Owner

@greptile review

@greptile-apps

greptile-apps Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 4/5

[Medium impact] Changes how the runner process exits after sending results.

Stop pending program callbacks during the final write before merging.

Findings

  1. P1 Background work blocks the result ▶
Fix with agent prompt
### Issue 1
runtime/runner.mjs:49
Waiting for stdout leaves pending program callbacks active. For example, `sleep(0).then(() => { while (true) {} }); return 'x'.repeat(1000000)` can start its loop while the result is still draining. The loop prevents the remaining writes and exit callback from running. The host then waits until the CPU limit kills the runner and receives an incomplete result.

Stop pending program callbacks when `finished` becomes true, while allowing stdout to drain.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

The runner now waits for its final stdout write before exiting.

  • The runner delivers large results before it exits.

Reviews (1) · Last reviewed commit: "Drain stdout before the runner exits, so..." · Reviewed by Greptile

Comment thread runtime/runner.mjs
finished = true
emit(msg)
process.exit(0)
process.stdout.write(lineOf(msg), () => process.exit(0))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Background work blocks the result

Waiting for stdout leaves pending program callbacks active. For example, sleep(0).then(() => { while (true) {} }); return 'x'.repeat(1000000) can start its loop while the result is still draining. The loop prevents the remaining writes and exit callback from running. The host then waits until the CPU limit kills the runner and receives an incomplete result.

Stop pending program callbacks when finished becomes true, while allowing stdout to drain.

Prompt To Fix With AI
This is a comment left during a code review.
Path: runtime/runner.mjs
Line: 49

Comment:
**Background work blocks the result**

Waiting for stdout leaves pending program callbacks active. For example, `sleep(0).then(() => { while (true) {} }); return 'x'.repeat(1000000)` can start its loop while the result is still draining. The loop prevents the remaining writes and exit callback from running. The host then waits until the CPU limit kills the runner and receives an incomplete result.

Stop pending program callbacks when `finished` becomes true, while allowing stdout to drain.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Moved to #71, out of scope for this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-gabe Needs Gabe's decision before work continues risk:t2-soft

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants