Summary
The GitHub Action (millionco/react-doctor@ced746f518f11e8283d488c4ff31c44e478bb0e5, v2.2.6, CLI 0.7.7) has been intermittently failing "Scan could not complete" on essentially every PR in our repo since 2026-07-12, regardless of diff content (docs-only, dependency-bump, and application-code diffs all affected). The action's own ensure-json-report.mjs fallback reports:
"error":{"name":"ReactDoctorActionError","message":"react-doctor exited with status 0 before producing a JSON report.","chain":[]}
Note the CLI's own recorded exit status is 0 (success) — the wrapper isn't seeing a crash, it's seeing a process that exited cleanly without ever writing a parseable JSON report to stdout.
What we ruled out (with direct evidence, not just hypothesis)
- Shallow checkout / unreachable base commit: added
fetch-depth: 0. This did eliminate the action's own "could not derive the PR's changed files from git ... base commit isn't reachable" warning on subsequent runs, but the scan continued to fail identically afterward. Not the cause of the report-file failure.
- Corrupted/poisoned
actions/cache toolchain install: deleted the react-doctor-toolchain-0.7.7-node24-Linux-X64 cache entry (repo-scoped GH Actions cache) to force a fresh npm install --prefix. Scan still failed identically on the next run.
- Corrupted persistent scan-result cache (
REACT_DOCTOR_CACHE_DIR, restored via broad cross-version restore-keys): deleted all react-doctor-scan-cache-* cache entries. Scan still failed identically.
- Install method / npx vs
npm install --prefix: reproduced the exact npm install --prefix $DIR --no-save --no-audit --no-fund react-doctor@0.7.7 step manually, matching flags (--json --json-compact --blocking none --project '*' --scope changed --changed-files-from <file>) and every env var the action forwards (REACT_DOCTOR_BASE_SHA, REACT_DOCTOR_CACHE_DIR, GITHUB_RUN_ID, REACT_DOCTOR_GITHUB_ACTION, REACT_DOCTOR_ACTION_*, NO_COLOR) — always succeeded (ok:true, valid JSON) both in a separate debug job and, most tellingly, in the same job/VM, immediately after the real scan step failed, re-running the exact same installed binary from ${RUNNER_TEMP}/react-doctor-toolchain/node_modules/.bin/react-doctor with the exact same flags against the exact same changed-files-from file — succeeded instantly.
What this points to
Since the identical binary, identical flags, identical files, identical environment, same VM, moments apart — fails once (empty/invalid report, exit 0) then succeeds immediately after — this looks like a timing-dependent stdout write race on process exit, not a deterministic bug tied to install state, cache state, or diff content. A likely mechanism: the CLI writes its JSON report to stdout and then calls (or reaches) process.exit() before an async stdout write has fully flushed to the redirected file — Node's documented behavior is that process.exit() does not wait for pending I/O, including writes to process.stdout/process.stderr, when the destination is non-blocking (this can apply even to regular-file redirects depending on how the write is issued, e.g. via a stream rather than a synchronous fs.writeSync).
Repro data
- Action pin:
millionco/react-doctor@ced746f518f11e8283d488c4ff31c44e478bb0e5 (v2.2.6)
- CLI version:
react-doctor@0.7.7
- Runner:
ubuntu-24.04, Node 24.18.0
- Flags:
--json --json-compact --blocking none --project '*' --scope changed --changed-files-from <file>
- Example failing run: https://github.com/felipewilliam2/AV-SITE/actions/runs/29285300247/job/86936280449 (see the "DEBUG - inspect report file and re-run CLI in-situ" step's log, later removed from our workflow — happy to share the raw log text if useful)
Suggested fix direction
If the CLI writes its final report via an async stream write (e.g. process.stdout.write(json) followed by process.exit(0)), switching to a synchronous write (fs.writeSync(1, json)) or awaiting the stream's drain/finish event before calling process.exit() would eliminate the race. Happy to help narrow this down further if you can point to where the CLI writes its final report and calls exit.
Summary
The GitHub Action (
millionco/react-doctor@ced746f518f11e8283d488c4ff31c44e478bb0e5, v2.2.6, CLI 0.7.7) has been intermittently failing "Scan could not complete" on essentially every PR in our repo since 2026-07-12, regardless of diff content (docs-only, dependency-bump, and application-code diffs all affected). The action's ownensure-json-report.mjsfallback reports:Note the CLI's own recorded exit status is 0 (success) — the wrapper isn't seeing a crash, it's seeing a process that exited cleanly without ever writing a parseable JSON report to stdout.
What we ruled out (with direct evidence, not just hypothesis)
fetch-depth: 0. This did eliminate the action's own"could not derive the PR's changed files from git ... base commit isn't reachable"warning on subsequent runs, but the scan continued to fail identically afterward. Not the cause of the report-file failure.actions/cachetoolchain install: deleted thereact-doctor-toolchain-0.7.7-node24-Linux-X64cache entry (repo-scoped GH Actions cache) to force a freshnpm install --prefix. Scan still failed identically on the next run.REACT_DOCTOR_CACHE_DIR, restored via broad cross-versionrestore-keys): deleted allreact-doctor-scan-cache-*cache entries. Scan still failed identically.npm install --prefix: reproduced the exactnpm install --prefix $DIR --no-save --no-audit --no-fund react-doctor@0.7.7step manually, matching flags (--json --json-compact --blocking none --project '*' --scope changed --changed-files-from <file>) and every env var the action forwards (REACT_DOCTOR_BASE_SHA,REACT_DOCTOR_CACHE_DIR,GITHUB_RUN_ID,REACT_DOCTOR_GITHUB_ACTION,REACT_DOCTOR_ACTION_*,NO_COLOR) — always succeeded (ok:true, valid JSON) both in a separate debug job and, most tellingly, in the same job/VM, immediately after the real scan step failed, re-running the exact same installed binary from${RUNNER_TEMP}/react-doctor-toolchain/node_modules/.bin/react-doctorwith the exact same flags against the exact samechanged-files-fromfile — succeeded instantly.What this points to
Since the identical binary, identical flags, identical files, identical environment, same VM, moments apart — fails once (empty/invalid report, exit 0) then succeeds immediately after — this looks like a timing-dependent stdout write race on process exit, not a deterministic bug tied to install state, cache state, or diff content. A likely mechanism: the CLI writes its JSON report to stdout and then calls (or reaches)
process.exit()before an async stdout write has fully flushed to the redirected file — Node's documented behavior is thatprocess.exit()does not wait for pending I/O, including writes toprocess.stdout/process.stderr, when the destination is non-blocking (this can apply even to regular-file redirects depending on how the write is issued, e.g. via a stream rather than a synchronousfs.writeSync).Repro data
millionco/react-doctor@ced746f518f11e8283d488c4ff31c44e478bb0e5(v2.2.6)react-doctor@0.7.7ubuntu-24.04, Node 24.18.0--json --json-compact --blocking none --project '*' --scope changed --changed-files-from <file>Suggested fix direction
If the CLI writes its final report via an async stream write (e.g.
process.stdout.write(json)followed byprocess.exit(0)), switching to a synchronous write (fs.writeSync(1, json)) or awaiting the stream'sdrain/finishevent before callingprocess.exit()would eliminate the race. Happy to help narrow this down further if you can point to where the CLI writes its final report and calls exit.