fix deadlock in shellcheck integration on darwin by using cmd.Stdin#651
Merged
rhysd merged 1 commit intorhysd:mainfrom Apr 19, 2026
Merged
Conversation
Writing the script to cmd.StdinPipe() before cmd.Start() relies on the kernel pipe buffer absorbing the whole payload. On darwin with several concurrent workers this deadlocks because the buffer fills up and no reader is attached until Start() runs, which never happens. Set cmd.Stdin to a strings.Reader instead so the standard library pipes the payload to the child after Start() on a copy goroutine. This is the canonical os/exec pattern and removes the manual pipe management. Closes rhysd#650
rhysd
approved these changes
Apr 19, 2026
Owner
rhysd
left a comment
There was a problem hiding this comment.
I could not reproduce the test failure by just copying&pasting the test case on my old Intel Mac. However this change looks more clean than previous and the CI passed. Merging.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #650.
cmdExecution.runused to create a stdin pipe withcmd.StdinPipe(), write the full script to it, close the writer, and only then callcmd.Output()which internally callscmd.Start(). That relies on the kernel pipe buffer being large enough to absorb the whole payload before a reader is attached. On darwin, under concurrent load, the buffer fills up and every worker goroutine ends up blocked oninternal/poll.(*FD).Write -> waitWriteinsideio.WriteString, so no child ever gets started and actionlint spins until killed.This replaces the manual pipe management with
cmd.Stdin = strings.NewReader(e.stdin).cmd.Output()/cmd.CombinedOutput()then spawn a copy goroutine afterStart(), which is the canonical os/exec pattern.The bug reproduces reliably on
darwin_arm64(tested on 1.7.10, 1.7.11, and 1.7.12 official release binaries) with any workflow that has ≥2run:steps whose scripts combined cross the pipe-buffer threshold — the smallest case I could trim it to was a 2 KB workflow with 50echo hellolines per step.Changes
process.go— replace the pre-Start()pipe write withcmd.Stdin = strings.NewReader(...). No behavior change on Linux, fixes the deadlock on darwin.process_test.go— addTestProcessConcurrentStdinDoesNotDeadlock, which runs 5 concurrentcatinvocations with a 64 KiB stdin payload guarded by a 10 s watchdog. Confirmed:main(watchdog fires at 10 s)Test evidence
Before the fix, running on my local
darwin/arm64M-series against the issue's 50-echo repro:After the fix (same binary built from this branch):
Full test suite (
go test ./...) still passes.