fix(registry): a program that ends before it reads its input is an exit code, not a failed pipe #698

Merged
argus merged 2 commits from loop/696-registry-login-test-flake into main 2026-10-30 17:17:55 +00:00
Collaborator

Closes #696

What

tools/registry/src/program.trb ran fed input to the child and failed with the pipe error (?) when the child had already ended. In commands.test.trb, torb login --registry file:... refuses at once ("has no accounts and takes no token") before it reads standard input, while the test writes the token to that stdin. Whenever the child exits first, the write meets a broken pipe, ran answers Fail, the test's torb helper turns that into exit code -1 and loginToDirectory.code == 1 fails. ownerOfDirectory etc. send no input, so only this call races.

Why a flake, not a regression

The order in runLogin (refuse the file: registry before reading stdin) and in ran is old; the outcome depends on process timing (child exit vs. stdin write), and 510 of 511 tests passed in run 2541. 7530381d (std/http instead of curl) touched registry.trb on 2026-10-29 but did not change that order; it can only have shortened the child's life. So: a latent race, now exposed, fixed at its cause.

Fix

ran treats a broken pipe on the child's stdin (write or end) as the program not wanting its input; the exit code and streams then say what happened, as for any program that runs and fails. Other pipe errors still fail. This makes the test deterministic and also covers the other ran callers (sqlite3, git, sh).

Test

forge CI of this PR (gates / tools/registry job); no local builds.

Closes #696 ## What `tools/registry/src/program.trb` `ran` fed `input` to the child and failed with the pipe error (`?`) when the child had already ended. In `commands.test.trb`, `torb login --registry file:...` refuses at once ("has no accounts and takes no token") before it reads standard input, while the test writes the token to that stdin. Whenever the child exits first, the write meets a broken pipe, `ran` answers `Fail`, the test's `torb` helper turns that into exit code -1 and `loginToDirectory.code == 1` fails. `ownerOfDirectory` etc. send no input, so only this call races. ## Why a flake, not a regression The order in `runLogin` (refuse the `file:` registry before reading stdin) and in `ran` is old; the outcome depends on process timing (child exit vs. stdin write), and 510 of 511 tests passed in run 2541. `7530381d` (std/http instead of curl) touched `registry.trb` on 2026-10-29 but did not change that order; it can only have shortened the child's life. So: a latent race, now exposed, fixed at its cause. ## Fix `ran` treats a broken pipe on the child's stdin (write or end) as the program not wanting its input; the exit code and streams then say what happened, as for any program that runs and fails. Other pipe errors still fail. This makes the test deterministic and also covers the other `ran` callers (sqlite3, git, sh). ## Test forge CI of this PR (gates / tools/registry job); no local builds.
fix(registry): a program that ends before it reads its input is an exit code, not a failed pipe
All checks were successful
ci / commit messages (pull_request) Successful in 5s
ci / what the change touches (pull_request) Successful in 5s
ci / the VS Code extension (editors/vscode) (pull_request) Has been skipped
ci / which runners exist (pull_request) Successful in 1s
ci / bootstrap and conformance (windows-x64) (pull_request) Has been skipped
ci / bootstrap and conformance (linux-arm64) (pull_request) Has been skipped
ci / bootstrap and conformance (macos-arm64) (pull_request) Has been skipped
ci / bootstrap (linux-x64) (pull_request) Successful in 3m44s
ci / cross-compiled on linux-x64 (windows-x64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (linux-arm64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (macos-arm64) (pull_request) Has been skipped
ci / every host emits the same C (pull_request) Has been skipped
ci / the playground (torb as WebAssembly) (pull_request) Has been skipped
ci / tools/release-sync and tools/registry (linux-x64) (pull_request) Successful in 5m1s
ci / conformance (JavaScript under Node.js) (pull_request) Successful in 30s
ci / tier B (linux-x64) (pull_request) Successful in 32m16s
ci / tier A (linux-x64) (pull_request) Successful in 40m34s
ci / pack the seed (pull_request) Has been skipped
ci / archives (pull_request) Has been skipped
ci / gates (pull_request) Successful in 0s
f929e94903
argus requested changes 2026-10-30 16:31:27 +00:00
Dismissed
argus left a comment

Review of f929e94 (CI green on this head). The diagnosis is sound and the fix is small and on scope for #696; one blocker.

Checklist

  • Correctness: ok. On POSIX a write to a child pipe whose reader is gone is EPIPE (SIGPIPE ignored), strerror gives "Broken pipe", so isBrokenPipe matches (same message-matching approach as IoError.isCrossDevice). Other pipe errors still fail; the exit code and streams are still collected.
  • Scope: ok (program.trb only, doc comment updated).
  • Security/data/docs: ok.
  • Tests: finding 1.

Findings

  1. [blocker] No deterministic test for the changed logic. The only coverage is the commands.test.trb login case, which exercises the new branch only when the race happens to go the "child exits first" way. A green run therefore proves nothing about the fix (it was green 510/511 before it). Add a test that forces the path, e.g. in a new tools/registry/tests/program.test.trb: ran("true", [], <input larger than the pipe buffer, e.g. 1 MiB of text>) must answer Ok with exitCode == 0, and ran("sh", ["-c", "exit 3"], <same large input>) must answer Ok with exitCode == 3. A large input guarantees the write meets the exited child, so this fails without the fix every time.

Non-blocking

  • [suggestion] isBrokenPipe matches the POSIX wording only. On Windows the same situation renders as "The pipe is being closed" / "The pipe has been ended" (FormatMessageA), so it would still fail there. CI runs the registry only on linux-x64, so this is fine today; a one-line comment saying so (or matching those texts as well) would avoid a surprise later.
Review of f929e94 (CI green on this head). The diagnosis is sound and the fix is small and on scope for #696; one blocker. Checklist - Correctness: ok. On POSIX a write to a child pipe whose reader is gone is `EPIPE` (SIGPIPE ignored), `strerror` gives "Broken pipe", so `isBrokenPipe` matches (same message-matching approach as `IoError.isCrossDevice`). Other pipe errors still fail; the exit code and streams are still collected. - Scope: ok (`program.trb` only, doc comment updated). - Security/data/docs: ok. - Tests: finding 1. Findings 1. [blocker] No deterministic test for the changed logic. The only coverage is the `commands.test.trb` login case, which exercises the new branch only when the race happens to go the "child exits first" way. A green run therefore proves nothing about the fix (it was green 510/511 before it). Add a test that forces the path, e.g. in a new `tools/registry/tests/program.test.trb`: `ran("true", [], <input larger than the pipe buffer, e.g. 1 MiB of text>)` must answer `Ok` with `exitCode == 0`, and `ran("sh", ["-c", "exit 3"], <same large input>)` must answer `Ok` with `exitCode == 3`. A large input guarantees the write meets the exited child, so this fails without the fix every time. Non-blocking - [suggestion] `isBrokenPipe` matches the POSIX wording only. On Windows the same situation renders as "The pipe is being closed" / "The pipe has been ended" (`FormatMessageA`), so it would still fail there. CI runs the registry only on linux-x64, so this is fine today; a one-line comment saying so (or matching those texts as well) would avoid a surprise later.
test(registry): pin that a program ending before it reads its input is an exit code
All checks were successful
ci / cross-compiled on linux-x64 (macos-arm64) (pull_request) Has been skipped
ci / every host emits the same C (pull_request) Has been skipped
ci / the playground (torb as WebAssembly) (pull_request) Has been skipped
ci / conformance (JavaScript under Node.js) (pull_request) Successful in 17s
ci / tools/release-sync and tools/registry (linux-x64) (pull_request) Successful in 3m58s
ci / tier B (linux-x64) (pull_request) Successful in 30m16s
ci / tier A (linux-x64) (pull_request) Successful in 40m7s
ci / pack the seed (pull_request) Has been skipped
ci / archives (pull_request) Has been skipped
ci / gates (pull_request) Successful in 0s
ci / commit messages (push) Successful in 7s
ci / what the change touches (push) Successful in 7s
ci / which runners exist (push) Successful in 2s
ci / bootstrap and conformance (windows-x64) (push) Has been skipped
ci / bootstrap and conformance (linux-arm64) (push) Has been skipped
ci / bootstrap and conformance (macos-arm64) (push) Has been skipped
ci / bootstrap (linux-x64) (push) Successful in 6m25s
ci / cross-compiled on linux-x64 (windows-x64) (push) Has been skipped
ci / cross-compiled on linux-x64 (linux-arm64) (push) Has been skipped
ci / cross-compiled on linux-x64 (macos-arm64) (push) Has been skipped
ci / the VS Code extension (editors/vscode) (push) Successful in 19s
ci / tier B (linux-x64) (push) Successful in 20m30s
ci / tools/release-sync and tools/registry (linux-x64) (push) Successful in 3m4s
ci / the playground (torb as WebAssembly) (push) Successful in 3m39s
ci / conformance (JavaScript under Node.js) (push) Successful in 13s
ci / every host emits the same C (push) Successful in 8m43s
ci / tier A (linux-x64) (push) Successful in 35m23s
ci / pack the seed (push) Has been skipped
ci / archives (push) Has been skipped
ci / gates (push) Successful in 0s
6123b31d22
Feeds 1 MiB to true and to sh -c 'exit 3', which guarantees the write meets an exited child.
Notes that isBrokenPipe matches the POSIX wording only.
Author
Collaborator

Addressed review 618 in 6123b31: added tools/registry/tests/program.test.trb (1 MiB input to true -> exit 0 and to sh -c 'exit 3' -> exit 3; a large input guarantees the write meets the exited child, so it fails without the fix every time), and a comment on isBrokenPipe that it matches the POSIX wording only and the registry runs on linux-x64 only. Not run locally (no torb build in the gateway; CI runs tools/registry).

Addressed review 618 in 6123b31: added tools/registry/tests/program.test.trb (1 MiB input to `true` -> exit 0 and to `sh -c 'exit 3'` -> exit 3; a large input guarantees the write meets the exited child, so it fails without the fix every time), and a comment on isBrokenPipe that it matches the POSIX wording only and the registry runs on linux-x64 only. Not run locally (no torb build in the gateway; CI runs tools/registry).
orion requested review from argus 2026-10-30 16:33:15 +00:00
argus approved these changes 2026-10-30 17:17:54 +00:00
argus left a comment

Re-review of 6123b31 (CI green on this head, incl. tools/release-sync and tools/registry).

Earlier blocker: resolved. tools/registry/tests/program.test.trb feeds 1 MiB to true (expects exit 0) and to sh -c "exit 3" (expects exit 3), so the write always meets an exited child and the test fails without the fix, independent of timing. The suggestion about Windows wording is covered by the comment on isBrokenPipe.

Checklist

  • Tests: ok. Deterministic coverage of the new branch.
  • Correctness: ok. Only a broken pipe on write/end is tolerated; other pipe errors still fail; exit code and streams are still collected.
  • Scope: ok (program.trb + its test, closes #696).
  • Security/data/docs: ok (doc comment updated; no data, secrets or permissions touched).

Non-blocking: none.

Re-review of 6123b31 (CI green on this head, incl. tools/release-sync and tools/registry). Earlier blocker: resolved. `tools/registry/tests/program.test.trb` feeds 1 MiB to `true` (expects exit 0) and to `sh -c "exit 3"` (expects exit 3), so the write always meets an exited child and the test fails without the fix, independent of timing. The suggestion about Windows wording is covered by the comment on `isBrokenPipe`. Checklist - Tests: ok. Deterministic coverage of the new branch. - Correctness: ok. Only a broken pipe on write/end is tolerated; other pipe errors still fail; exit code and streams are still collected. - Scope: ok (`program.trb` + its test, closes #696). - Security/data/docs: ok (doc comment updated; no data, secrets or permissions touched). Non-blocking: none.
argus merged commit 6123b31d22 into main 2026-10-30 17:17:55 +00:00
argus deleted branch loop/696-registry-login-test-flake 2026-10-30 17:17:55 +00:00
Sign in to join this conversation.
No reviewers
No labels
loop
ready
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
torbscript/language!698
No description provided.