Skip to content

Makes a stdio connection refusal that nothing diagnosed fail CI - #13

Merged
jserv merged 7 commits into
mainfrom
stdio
Aug 28, 2026
Merged

Makes a stdio connection refusal that nothing diagnosed fail CI#13
jserv merged 7 commits into
mainfrom
stdio

Conversation

@jserv

@jserv jserv commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Summary by cubic

Hardens the stdio gate so an unexplained Connection refused or missing, empty, or unreadable suite log fails instead of passing. It also adds a RustSec lockfile scan and prevents self_check probe failures from appearing as successful not_probed reports.

CI and reliability

  • Diagnosed bind/listen races remain allowed, but their recovery count is logged under the same RUST_LOG setting in CI and scripts/run-gates.sh.
  • The probe and stdio connection paths now share the never_listened diagnosis and a bounded retry budget.
  • rustsec/audit-check@v2 scans the tracked lockfile with job-scoped checks: write; fork pull requests skip the action because their tokens cannot create checks.
  • Workflow guards now share one parser and verify that every lane gates the release.
  • killpg EPERM errors get a dedicated teardown log without changing cleanup behavior.

Format and cleanup

  • Removes the always-empty prog_public, pragmas, and texts F-CIL fields and bumps the format to fcil-1.1; consumers must handle the new schema version.
  • Stops serializing the discarded compute_topological_order result.

Written for commit b8d9b02. Summary will update on new commits.

Review in cubic

@jserv jserv changed the title Stdio Makes a stdio connection refusal that nothing diagnosed fail CI Aug 28, 2026
cubic-dev-ai[bot]

This comment was marked as resolved.

jserv added 7 commits August 28, 2026 20:09
"prog_public", "pragmas" and "texts" arrived with the initial import as
empty lists and were never filled, so a reader could only conclude that a
project has no public symbols, no pragmas and no texts, which is a
different statement from the field not being implemented. Nothing in this
tree or outside it consumes the dump.
"compute_topological_order" is called for the order it writes into
session state, and "tool_result_json" then built a value from its answer
that the next line discarded.
The comment reached one meaning by eliminating three cases measured on
macOS 25.6: a killpg against a group that never existed answers ESRCH, so
does one against a child that has exited and been reaped, and a zombie
still in its group answers success. macOS kill(2) names a fourth in its
own EPERM entry, "When signaling a process group, this error is returned
if any members of the group could not be signaled", so a group that is
entirely ours and was entirely signaled answers EPERM when one member was
mid-reap. That is what an ordinary teardown does.

Both meanings are stated now and EPERM gets its own arm and its own
message. No behavior change: nothing on this path retried or escalated
before and nothing does now.
This file had four hand parses of "which lines name a job", and its own
"step_command" comment records what that costs: three copies of one
parsing rule was three chances to fix it in two places, and a drift
between them put the parser gap back without any copy failing.

The copies differed in which trap they fell into. Two read a comment line
as a job, so a commented-out "  # release:" named one. Two tested the raw
line for a closing colon, so a header carrying a trailing comment stopped
being a header and its job merged into the one above it, which is a guard
that keeps passing while it has stopped checking that job.

"workflow_jobs" is the survivor and it fixes a third trap none of them
had: a comment at column zero is not a key, and treating it as one ended
the jobs scan and dropped every job below it.

Three more parses were spelled twice and are now shared. "sorted_files"
carries the directory read that "ci_command_text" and "workflow_files"
both did. "all_workflow_jobs" answers "every job in every workflow" once.
"flow_list" reads a YAML flow list, which had already drifted: the matrix
parse strips quotes and nothing else did.

The parser's own property, that it reads jobs and not the on: trigger
keys sitting at the same indent, moves from a stray assertion inside the
cppo guard to a test of the parser.
The bind/listen race is retried in "connect_when_listening", and its
deadline says "never listened" on the same line as the io error. A
"Connection refused" without that qualifier came from a path the retry
does not reach, so "check-stdio-refusal.sh" fails on one. It runs from
the workflow and from "run-gates.sh" under the same "want stdio", so the
suite cannot run without its check, and it treats an absent, empty or
unreadable log as a failure rather than as nothing to scan. grep answers
1 for no match and 2 for a failure to read, and collapsing those is how a
gate passes by not running.

Four things the check needed before it could mean anything.

"probe_requests" reported the same race in its own words, so the guard
would have gone red on the known bug. Both sites build the message
through "never_listened" now, and both count absorbed refusals and warn
on recovery, so a race the probe swallows is no longer invisible to the
drift count. Its deadline is 120 times shorter than the spawn's, which
makes it the likelier of the two to trip.

The recovered race left no trace at all. It is a tracing warn against an
EnvFilter that admits ERROR only when RUST_LOG is unset, so the stdio
step sets it, "run-gates.sh" matches, and a guard pins the two equal:
drop it from either and the script reports zero races for every run,
which is what a healthy run reports.

Aligning the probe's wording put it inside the allowlist, and unlike
"connect_when_listening", whose message rides an Err into a failed tool
call, a probe failure is only a field in a payload. A probe timeout would
have turned all fifty requests into "not_probed" and gone green. The new
stdio test asserts on the reason, so the by-design skips still pass.

The scan runs only when the suite ran. Without the step outcome test,
"!cancelled()" would also fire when an earlier failed step skipped the
suite, and scanning a log nobody wrote is what forced the earlier version
to tolerate a missing one.
"checks: write" is scoped to artifact-scans alone, the way release scopes
"contents: write", because the action reports by creating a check run and
the workflow floor is "contents: read". Skipped on pull requests from
forks, where GitHub caps the token at read-only whatever the block says,
so the action fails there on a clean tree; a dependency arriving that way
is still scanned on the push that merges it.

Deliberately not in "run-gates.sh": its verdict comes from a database
that moves on its own rather than from the tree, so the same commit can
pass today and fail tomorrow. That also puts it outside every existing
guard, since "gate_of" reads cargo commands and "scripts/" paths and not
"uses:" steps, so a guard pins the action and its permission together.
Checking them separately would let the permission move to any other job
while the guard stayed green.
A job that runs and is not among the release job's needs can be red while
the rolling tag is republished, which is a green badge over a binary
nothing vouched for.

A job that deliberately does not gate a release will fail here. That is
the intent: it is a decision worth writing down rather than one worth
inferring from an absence.
@jserv
jserv merged commit ae73634 into main Aug 28, 2026
9 checks passed
@jserv
jserv deleted the stdio branch August 28, 2026 12:32
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