Skip to content

fix: survive the instant-exit pty race; report signal deaths - #5

Merged
vyncint merged 1 commit into
mainfrom
fix/exit-signal-forensics
Aug 9, 2026
Merged

vyncint merged 1 commit into
mainfrom
fix/exit-signal-forensics

Conversation

@vyncint

@vyncint vyncint commented Aug 9, 2026

Copy link
Copy Markdown
Owner

The stress workflow caught the suite flaking at ~1/80 instant-exit
spawns (child writes and exits within its first millisecond): the OS
pty teardown — macOS especially — can discard output still buffered at
slave close, and can surface teardown as a signal-death instead of the
real exit code. Reproduced locally at ~1/10 full-suite runs.

Harness fixes:

  • attach the reader thread BEFORE spawning the child, so the drain is
    running from the child's first byte (narrows the race as far as
    userspace can; the residual kernel-side caveat is documented in
    docs/DESIGN.md and README known-limitations)
  • ExitStatus now carries the terminating signal's name; Display says
    'killed by signal: ...' so harness-level kills can never be mistaken
    for application exit codes (ExitStatus: Clone, no longer Copy)
  • Screen's Debug is the compact header+text rendering; a failing
    Result test prints a readable screen instead of a single-line dump
    of thousands of cells (long enough that CI log pipelines dropped it,
    which is why the first stress failures were undiagnosable)

Suite fixes:

  • every instant-exit script now ends with a stdin 'read' guard and is
    released with Enter only after its output is asserted — the
    deterministic pattern, now documented in DESIGN.md; doctests teach it
  • unicode-torture gained the same guard
  • resize-echo initializes crossterm's event source (the SIGWINCH
    listener) before its first draw, closing a resize-before-listener
    lost-signal race
  • fixture_bin() always refreshes fixture binaries once per process:
    cargo test does not relink plain bin artifacts, so an old
    target// from a previous cargo build silently tested
    stale fixture code
  • exit-code assertions include the full status in their message

Validation: 30/30 full-suite release iterations at --test-threads=4
clean locally (previous flake rate ~1/10); signal reporting covered by
a new signal-death test.

Signed-off-by: Vyncint Ng vyncint@icloud.com

The stress workflow caught the suite flaking at ~1/80 instant-exit
spawns (child writes and exits within its first millisecond): the OS
pty teardown — macOS especially — can discard output still buffered at
slave close, and can surface teardown as a signal-death instead of the
real exit code. Reproduced locally at ~1/10 full-suite runs.

Harness fixes:
- attach the reader thread BEFORE spawning the child, so the drain is
  running from the child's first byte (narrows the race as far as
  userspace can; the residual kernel-side caveat is documented in
  docs/DESIGN.md and README known-limitations)
- ExitStatus now carries the terminating signal's name; Display says
  'killed by signal: ...' so harness-level kills can never be mistaken
  for application exit codes (ExitStatus: Clone, no longer Copy)
- Screen's Debug is the compact header+text rendering; a failing
  Result test prints a readable screen instead of a single-line dump
  of thousands of cells (long enough that CI log pipelines dropped it,
  which is why the first stress failures were undiagnosable)

Suite fixes:
- every instant-exit script now ends with a stdin 'read' guard and is
  released with Enter only after its output is asserted — the
  deterministic pattern, now documented in DESIGN.md; doctests teach it
- unicode-torture gained the same guard
- resize-echo initializes crossterm's event source (the SIGWINCH
  listener) before its first draw, closing a resize-before-listener
  lost-signal race
- fixture_bin() always refreshes fixture binaries once per process:
  cargo test does not relink plain bin artifacts, so an old
  target/<profile>/<name> from a previous cargo build silently tested
  stale fixture code
- exit-code assertions include the full status in their message

Validation: 30/30 full-suite release iterations at --test-threads=4
clean locally (previous flake rate ~1/10); signal reporting covered by
a new signal-death test.

Signed-off-by: Vyncint Ng <vyncint@icloud.com>
@vyncint
vyncint merged commit b2a384f into main Aug 9, 2026
9 checks passed
@vyncint
vyncint deleted the fix/exit-signal-forensics branch August 9, 2026 06:13
vyncint added a commit to DevanshGang/termlens that referenced this pull request Sep 13, 2026
The label link pointed at `github.com/vyncint/temlens` (missing the `r`),
which is a 404 — and vyncint#314's "Done when" asks specifically that the label
link resolve, so the section did not yet do the one job it exists for.

Alongside that:

- Restore the missing spaces around the README link and the `)label` run-on,
  and drop the stray space before the comma.
- Move the section below the four-projects callout so the framing note still
  sits directly under the intro, and use the house em-dash.
- Point the DCO mention at §5 rather than restating it, and keep the section
  unnumbered so §1–§8 keep their numbers: the PR template links §5 and §6.
- In the README, append the pointer to the sentence instead of splitting it
  mid-clause, since the "Three things to know" list follows immediately.

Verified: both URLs return 200, `vyncint#5-developer-certificate-of-origin-dco`
and `#where-to-start` match real headings, and check-readme-links.sh passes.

Signed-off-by: Vyncint Ng <115854244+vyncint@users.noreply.github.com>
vyncint added a commit that referenced this pull request Sep 17, 2026
* fix(ci): check the pull-request template's links

.github/PULL_REQUEST_TEMPLATE.md linked the two checklist items a
contributor is most likely to click as `../CONTRIBUTING.md`. That is right
for a file living in `.github/`, and wrong for a pull-request body, which
GitHub renders relative to the pull request's own URL: from
`//pull/351` it resolved to
`/vyncint/termlens/CONTRIBUTING.md`, a 404, on the two rules this
repository blocks a merge over — the DCO sign-off and the
no-AI-attribution policy.

Both links are absolute now, with the anchors checked against the headings
in CONTRIBUTING.md (`#5-developer-certificate-of-origin-dco`,
`#6-ai-tooling-policy`), and `check-readme-links.sh` gives the template
the absolute-only rule `README.md` already has, for its own URL. The
gate's selftest gains a relative-template fixture and asserts the
repository's own files — the template among them — still pass; `ci.yml`
runs the gate over it, and CONTRIBUTING §1 lists the wider invocation.

`.github/ISSUE_TEMPLATE/` was checked for the same shape: its only link is
`config.yml`'s, which is already absolute.

Closes #369

Signed-off-by: whiteye <62688683+maskjelly@users.noreply.github.com>

* test(link-gate): make the template fixture isolate the new rule

The fixture linked `../CONTRIBUTING.md`, which the gate already rejected
before this branch: relative targets resolve against the repository root,
and `$root/../CONTRIBUTING.md` does not exist. The expectation compares
exit status only, so a fixture that goes red for the old reason satisfies
it — reverting check-readme-links.sh left the selftest 7/7 green.

Link `CONTRIBUTING.md` instead. It resolves from the root, so the old
rule accepts it and only the new absolute-only rule can reject it. With
the script reverted the selftest now fails, which is the point of it:

  FAIL  PULL_REQUEST_TEMPLATE.md: a relative target fails (#369)  exit 0, expected nonzero

Same shape as readme-relative/, which links docs/DESIGN.md for exactly
this reason.

Signed-off-by: Vyncint Ng <vyncint@icloud.com>

---------

Signed-off-by: whiteye <62688683+maskjelly@users.noreply.github.com>
Signed-off-by: Vyncint Ng <vyncint@icloud.com>
Co-authored-by: Vyncint Ng <vyncint@icloud.com>
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.

1 participant