Skip to content

fix(format): restore stdout/stderr ignore for formatter processes#26037

Merged
rekram1-node merged 2 commits intoanomalyco:devfrom
ferdinandyb:fix-formatter-pipe
May 7, 2026
Merged

fix(format): restore stdout/stderr ignore for formatter processes#26037
rekram1-node merged 2 commits intoanomalyco:devfrom
ferdinandyb:fix-formatter-pipe

Conversation

@ferdinandyb
Copy link
Copy Markdown
Contributor

Issue for this PR

Closes #26032

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

The ChildProcessSpawner migration in 5cd54ec dropped stdout/stderr: "ignore" when refactoring the formatter to use Effect's spawner. The old Process.spawn defaulted stdout and stderr to "ignore" (not "pipe"):

stdio: [opts.stdin ?? "ignore", opts.stdout ?? "ignore", opts.stderr ?? "ignore"]

It also resolved on the "exit" event, which fires when the process itself exits. ChildProcessSpawner instead defaults to "pipe" for all stdio and resolves on the "close" event, which only fires after every file descriptor referencing the pipes is closed — including those held by grandchild processes that inherited them.

If a formatter like pants spawns worker subprocesses, those children keep the pipe FDs open after the formatter exits. "close" never fires, the Deferred that handle.exitCode awaits is never resolved, and the edit/write tool hangs indefinitely.

Restoring stdout/stderr: "ignore" avoids creating pipes entirely, so "close" fires immediately when the formatter process exits regardless of any background children it may have spawned.

How did you verify your code works?

I've verified manually and also added a test. Manual verification included manually killing the held process in the original version of the code (which is when an edit actually managed to finish instead of hanging) and also verified the new code indeed finishes.

Screenshots / recordings

N/A

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

If you do not follow this template your PR will be automatically rejected.

The ChildProcessSpawner migration in 5cd54ec dropped stdout/stderr:
"ignore" when refactoring the formatter to use Effect's spawner.

This was an accidental omission. The old Process.spawn defaulted
stdout and stderr to "ignore" (not "pipe"):

  stdio: [opts.stdin ?? "ignore", opts.stdout ?? "ignore", opts.stderr ?? "ignore"]

It also resolved on the "exit" event, which fires when the process
itself exits. ChildProcessSpawner instead defaults to "pipe" for all
stdio and resolves on the "close" event, which only fires after every
file descriptor referencing the pipes is closed — including those held
by grandchild processes that inherited them.

If a formatter like pants spawns worker subprocesses, those children
keep the pipe FDs open after the formatter exits. "close" never fires,
the Deferred that handle.exitCode awaits is never resolved, and the
edit/write tool hangs indefinitely.

Restoring stdout/stderr: "ignore" avoids creating pipes entirely, so
"close" fires immediately when the formatter process exits regardless
of any background children it may have spawned.

Fixes: anomalyco#26032
@rekram1-node
Copy link
Copy Markdown
Collaborator

/review

@rekram1-node rekram1-node merged commit 293bb42 into anomalyco:dev May 7, 2026
8 checks passed
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 7, 2026

lgtm

capyBearista pushed a commit to capyBearista/opencode that referenced this pull request May 8, 2026
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.

Formatter hangs indefinitely when spawned command has background child processes (stdout/stderr pipe never closes)

2 participants