Repository navigation
the text step report can span SEVERAL lines, so plugin-check reads no verdict and every check phase of a bed SKIPs #282
Description
The text step report can span SEVERAL lines, so plugin-check reads no verdict from a bed's
report and every check phase of that bed SKIPs — the bed's exit code is 3, not 0.
Where it was found. The R10 gate of opencharly/layer-docs-site#13 (charly check run check-docs) — a change in a DIFFERENT repo, whose only bed is check-docs. The bed's five
check phases all SKIP with the same reason, and the run's verdict is SKIP:
charly check run check-docs: [check-image] SKIP after 14.907s: the child's report could not be
read: 21 step line(s) against the 22 step(s) its own rollup counted
charly check run check-docs: [check-live] SKIP after 7.458s: the child's report could not be
read: 24 step line(s) against the 25 step(s) its own rollup counted
charly check run check-docs: SKIP (steps=16) — check-image: ...; check-live: ...;
feature-run: ...; check-live-rebuild: ...; feature-run-rebuild: ...
Expected. A step is ONE line: the shape spec/report's renderStep writes — two spaces,
the status word, the keyword, the text, two spaces, the id in brackets, the message. A reader
that parses the report line by line can therefore pair every status with its id.
Actual. The report's rollup line says 22 steps: 18 passed, 0 failed, 4 skipped, but only
21 of those 22 steps are on a line the reader can read. The missing one is a run: step whose
TEXT carries newlines: renderStep writes step.Text verbatim, so the step's text spills over
the following lines and the id bracket lands on a line of its own.
From the child's own report (.check/check-docs/2026.302.0258/check-image.log):
SKIP run command=case "${BUILD_ARCH}" in
x86_64) PNPM_ARCH="x64" ;;
aarch64) PNPM_ARCH="arm64" ;;
*) echo "Unsupported arch: ${BUILD_ARCH}"; exit 1 ;;
esac
curl -fsSL "https://github.com/pnpm/pnpm/releases/download/v${PNPM_VERSION}/pnpm-linux-${PNPM_ARCH}" -o /usr/local/bin/pnpm
chmod 0755 /usr/local/bin/pnpm
[plan:candy:github.com/opencharly/layer-nodejs:0] skipped — verify-only mode (mutating step)
That first block is ONE step (the run: step of the layer-nodejs candy, whose run: text is a
multi-line command= copy of its own command: block). Its status word is on the first line and
its id bracket is on the last, so no single line carries both.
Mechanism. opencharly/plugin-check's reader (bed_status.go) matches ONE line per step:
var checkOutcomeRe = regexp.MustCompile(`^ {2}(PASS|FAIL|SKIP|INFRA) +([A-Za-z][A-Za-z0-9_-]*) +(.*?) {2}\[([^\[\]]*)\](.*)$`)The step above matches no line, so rep.Printed is 21 while the report's own rollup says 22.
The reader then refuses the whole report ("a report that does not agree with itself cannot say
what the run proved" — opencharly/plugin-check#134) and credits NOTHING from it, so every phase
that reads a child report becomes a SKIP.
Missed control. The producer of the format does not guarantee the shape the consumers
parse. spec/report's renderStep writes step.Text and the step message verbatim; only the
MESSAGE is length-truncated (truncate(msg, 200)), and truncation does not remove a newline.
Nothing in spec/report asserts "a step is one line", and nothing in its tests renders a step
whose text carries a newline. The defect was LATENT until plugin-check v0.2026298.205 (the
reader behind opencharly/plugin-check#134) added the "the report must agree with its own
rollup" self-check, which turned a silent partial read into a loud SKIP. The older reader
(v0.2026295.2128, the driver of the previous check-docs run in opencharly/layer-docs-site#12)
credited 21 of the 22 steps and never noticed.
Blast radius. Every bed whose plan carries a step whose text (or whose result message)
contains a newline — the layer-nodejs candy's run: step is one, and the case/command=
pattern is common. The JSON and TAP reports carry the same text; TAP is line-based too and is
broken the same way.
Root cause. The report format's producer lets a step occupy more than one line. The format
is a CONTRACT (opencharly/plugin-check#135 says so: "the verdict reader parses the engine's
report as TEXT: a change in spec/report's text format silently changes every bed's verdict"),
and this repo owns it.
Root fix. spec/report must guarantee the shape it publishes: render EVERY step on ONE
line — flatten the step text and the step message (a newline becomes a space, or another
one-line marker) — and assert the invariant in this repo's own tests: a step whose text carries
newlines still renders as exactly one line, that line still carries the id bracket, and the
number of step lines equals the rollup's count. The TAP reporter gets the same treatment; the
JSON and JUnit reports are structured and unaffected.
Classification. Blocking for the change that found it (opencharly/layer-docs-site#13 — its
only bed cannot pass), but the defect is NOT introduced by that change: the base tree
(opencharly/layer-docs-site 63271d4, without the change) produces the IDENTICAL skip, with
the identical numbers and the identical exit code 3. It is a PRE-EXISTING defect in the check
engine's report format, in this repo, and it is fixed here — producer-first — before the change
that found it is landed.
Reproduction.
- Build a charly from
opencharly/charlyorigin/main(the driver:2026.301.1936). charly check run check-docsinopencharly/layer-docs-siteat63271d4— the exit code is
3 and every check phase SKIPs with the reason above.- The same run on a tree whose plan has no multi-line step text reads its report fine — the
difference is the newline, nothing else.
Agent: charly · session ses_8efb271d-f2c0-47d3-85e3-2a1e00b11ae0
Activity
aitrawog commentedon Oct 29, 2026
CLAIM — this is charly's session
ses_8efb271d-f2c0-47d3-85e3-2a1e00b11ae0.Found while running opencharly/layer-docs-site#13's R10 bed (
charly check run check-docs): all fivecheck phases SKIP with "the child's report could not be read: 21 step line(s) against the 22
step(s) its own rollup counted", and the run's exit code is 3.
RCA:
spec/report'srenderStepwritesstep.Textverbatim, so a step whose text carries anewline spans several lines and its id bracket lands on a line of its own.
plugin-check'sone-line reader then matches no line for that step, the report's own rollup disagrees with the
number of step lines, and (since plugin-check#134) the whole report is refused — so no phase
credits anything and the bed SKIPs. It is the report FORMAT's producer that does not guarantee
the shape the consumers parse, and this repo owns that format.
Root fix:
spec/reportrenders every step on exactly ONE line (text and message flattened),and this repo's own tests assert the invariant — one line per step, the id bracket on that
line, and the step-line count equal to the rollup's count. The TAP reporter gets the same
treatment; the structured reports are unaffected.
Note the base tree (opencharly/layer-docs-site
63271d4, WITHOUT the change that found this)produces the identical skip with the identical numbers and the identical exit code 3 — so the
defect is pre-existing and is being fixed here, producer-first, before the change that found
it is landed.
Agent: charly · session
ses_8efb271d-f2c0-47d3-85e3-2a1e00b11ae0aitrawog commentedon Oct 29, 2026
STATUS
Agent: charly · session
ses_8efb271d-f2c0-47d3-85e3-2a1e00b11ae0Head.
f222b752eb80838777e2afbf6540c3c552677a38onfix/report-one-line-per-step, pushed as#283.
Done. The producer-side fix is implemented, tested and committed in this repo:
spec/reportgainsOneLine— the one flattening rule for the report — andrenderStepwrites a step's keyword, text, id and message through it, so a step is EXACTLY one line and
its status and its id are on the SAME line. The TAP reporter gets the same treatment; the
JSON and JUnit reports are structured and keep the text as data.
truncatenow cuts on a CHARACTER boundary. It cut on BYTES, so a multi-byte rune could becut in half and the report carried invalid UTF-8 — a line a reader cannot decode, in a
report whose whole value is that a reader can read it.
origin/main's renderer fails all three new tests,and the rendered report it produces (8 step lines for 2 steps) is pasted in the PR body
next to the fixed one.
The repo's whole gate is green on the committed tree —
go build ./...,go test ./...(25 packages),
gofmt,golangci-lint(0 issues) andcharly task cue-gen(a no-opregeneration,
TestGenReproduciblepassing). All outputs are pasted in the PR body.Next. The consumer legs of the producer-first chain, in this order: charly's
specpinbump → a worktree-local rebuild of the charly binary →
charly check run check-docsGREEN onopencharly/layer-docs-site#13's committed tree → opencharly/layer-docs-site#13 lands. This
issue closes when the fix is merged and tagged here.
Next update: when the validator's verdict on #283 lands, or when the charly leg starts —
whichever comes first.
Assisted-by: Claude Code minimax/minimax-m3.1-flash:cloud (fully tested and validated)
aitrawog commentedon Oct 29, 2026
RESOLVED
Agent: charly · session
ses_8efb271d-f2c0-47d3-85e3-2a1e00b11ae0Fixed and merged. #283 — squash
4266c8b, module tagv0.2026302.327— landedat 2026-10-29T03:27:15Z. The
charly/pr-validatorreturned PASS, the repo's own CI (build,test, gofmt, golangci-lint) is green, and the regeneration gate is a no-op on the committed
tree.
What landed.
spec/reportgainedOneLine— the one flattening rule for the report —and
renderStepnow renders a step's keyword, text, id and message through it, so a step isEXACTLY one line and its status and its id are on the SAME line. The TAP reporter flattens the
same way.
truncatenow cuts on a CHARACTER boundary rather than a byte one, so a report lineis always valid UTF-8. The JSON and JUnit reports are structured and keep the step's text as
data.
The invariant is now asserted in this repo's own tests — one line per step, the id bracket
on exactly one line, that line carrying the status, the step-line count equal to the rollup's
count, and a TAP point that never spills over itself. Those tests FAIL against the old
renderer: the before/after reports are pasted in the PR's body.
The reader was not touched. Producer-first: the format's producer now guarantees the shape
the readers already require, and the reader that refused a self-inconsistent report keeps
refusing one — it is now never handed one.
Still open, and correctly so: opencharly/plugin-check#135 (the verdict reader is a TEXT
parser bound to another repo's rendering — the CLASS this instance was, which the producer-side
fix removes but does not retire), and opencharly/layer-docs-site#13 (whose
check-docsbed isgreen once the charly leg carries this version into the binary).
Assisted-by: Claude Code minimax/minimax-m3.1-flash:cloud (fully tested and validated)