Skip to content

the text step report can span SEVERAL lines, so plugin-check reads no verdict and every check phase of a bed SKIPs #282

Closed

Description

@aitrawog

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.

  1. Build a charly from opencharly/charly origin/main (the driver: 2026.301.1936).
  2. charly check run check-docs in opencharly/layer-docs-site at 63271d4 — the exit code is
    3 and every check phase SKIPs with the reason above.
  3. 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

  1. aitrawog commented on Oct 29, 2026

    @aitrawog
    ContributorAuthor

    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 five
    check 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's renderStep writes step.Text verbatim, so a step whose text carries a
    newline spans several lines and its id bracket lands on a line of its own. plugin-check's
    one-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/report renders 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-2a1e00b11ae0

  2. self-assigned this
    on Oct 29, 2026
  3. aitrawog commented on Oct 29, 2026

    @aitrawog
    ContributorAuthor

    STATUS

    Agent: charly · session ses_8efb271d-f2c0-47d3-85e3-2a1e00b11ae0

    Head. f222b752eb80838777e2afbf6540c3c552677a38 on fix/report-one-line-per-step, pushed as
    #283.

    Done. The producer-side fix is implemented, tested and committed in this repo:

    • spec/report gains OneLine — the one flattening rule for the report — and renderStep
      writes 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.
    • truncate now cuts on a CHARACTER boundary. It cut on BYTES, so a multi-byte rune could be
      cut 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.
    • The coverage fails without the change: 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) and charly task cue-gen (a no-op
    regeneration, TestGenReproducible passing). All outputs are pasted in the PR body.

    Next. The consumer legs of the producer-first chain, in this order: charly's spec pin
    bump → a worktree-local rebuild of the charly binary → charly check run check-docs GREEN on
    opencharly/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)

  4. aitrawog commented on Oct 29, 2026

    @aitrawog
    ContributorAuthor

    RESOLVED

    Agent: charly · session ses_8efb271d-f2c0-47d3-85e3-2a1e00b11ae0

    Fixed and merged. #283 — squash 4266c8b, module tag v0.2026302.327 — landed
    at 2026-10-29T03:27:15Z. The charly/pr-validator returned 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/report gained OneLine — the one flattening rule for the report —
    and renderStep now renders 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 flattens the
    same way. truncate now cuts on a CHARACTER boundary rather than a byte one, so a report line
    is 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-docs bed is
    green once the charly leg carries this version into the binary).

    Assisted-by: Claude Code minimax/minimax-m3.1-flash:cloud (fully tested and validated)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions