Skip to content

fix(land): a gate that exits before reading stdin is not a refusal (CLOUD-995) - #673

Merged
wenzowski merged 4 commits into
mainfrom
claude/groom-cloud-913-vth4kx
Aug 23, 2026
Merged

wenzowski merged 4 commits into
mainfrom
claude/groom-cloud-913-vth4kx

Conversation

@wenzowski

@wenzowski wenzowski commented Aug 23, 2026 •

Copy link
Copy Markdown
Contributor

Closes CLOUD-995.
Closes CLOUD-869.

DO-NOT-CLOSE CLOUD-858

CLOUD-858 is served by the memory commit but not completed by it: the rule that
commit records was measured on CLOUD-858, while that row's headline acceptance
— graph-check over the whole Todo closure — has still never been run. Closing
it here would move a row a column ahead of its own evidence.

CLOUD-869 does close. An earlier draft of this body said Refs: instead, on the
reasoning that its acceptance greps already pass on main so closing it from
here would take credit for another branch's landing. That reasoning strands the
row, which is the defect CLOUD-674 exists to catch, and the gate caught it: the
row is open, its last unmet piece is the install.sh commit below, and after
this lands there is nothing left in it to do.

land reported a refusal that never happened

land runs under pipefail and piped the PR body into three gates. Two of them
can exit before they read it:

  • filed-here-check returns 0 at its line 173 when BATTEN_FILED_HERE_BYPASS is
    set, and again on a missing git dir or a detached HEAD — but does not read
    stdin until line 288.
  • closing-key-check exits 2 on three could-not-read paths, all before its read.

The producer then takes EPIPE, pipefail promotes it to a failed pipeline, and
land reports the gate as having refused. So the bypass inverted the gate it
exists to switch off: BATTEN_FILED_HERE_BYPASS=1 made the lap die with "filed
a row that was never groomed to Ready"
.

Here-strings remove the pipeline, so the status is the gate's own and no longer
depends on whether it drains first. land.sh:1033 already prescribes exactly
this, for exactly this reason; the three call sites were the shape its comment
warns about.

Shown able to fail

The new case reddens against the piped shape with bash's own diagnosis:

land.sh: line 1476: printf: write error: Broken pipe
::error:: land: #150 defers a decision with no ticket.

and passes against the fix. The 100k body is load-bearing: a short one fits the
kernel pipe buffer, the producer finishes before the reader is gone, and the old
shape passes too.

That size dependence is also why this surfaced when it did. Case 1350's body
is short, so the write normally completes — under the parallel four-worker suite
the scheduler decided the race the other way. Mechanism and trigger are separate
here: the mechanism is SIGPIPE under pipefail, the trigger is load. The
load-sensitivity itself stays with CLOUD-426 and CLOUD-449.

Correcting the record

CLOUD-995 was filed claiming main was red and the case failed deterministically
in isolation. Both were wrong, and the row now says so at the top. The isolated
runs behind "deterministic" invoked bats directly, bypassing mise, so
mise.toml [env] never applied and all 60 runs died on an unset
CI_REQUIRED_CHECKS — an artefact of the harness. Through mise exec, the case
is 30/30 green on unmodified main, and main advanced twice while this branch
was open, so the fleet was landing throughout.

Also here

  • install.sh — Refs: CLOUD-869. Three sites still said a token is required
    because the repository is private: the header comment, the --help text, and
    the release-API failure message. That row's own acceptance greps are scoped to
    markdown and tracker links, so nothing on it could see a shell string. Its
    markdown half landed from another branch while this one was open.
  • mem:workflow/board-states — Refs: CLOUD-858. Where a row's prose prescribes
    a repair and its own Acceptance says otherwise, the Acceptance wins.

Verification

  • CLOUD-995 case passes on this branch, fails against the piped shape
  • grep -in private install.sh — empty
  • mise run verify green before readying

@linear-code

linear-code Bot commented Aug 23, 2026 •

Copy link
Copy Markdown
CLOUD-869 The front door tells the reader the repository is private and cites four documents they cannot open

Small, and it is the first thing a public reader sees. Three defects. The first two go false the moment CLOUD-585 lands; the third is false already.

Claims that stop being true

  • README.md:23-27 — "the repository is private, so there is no public install path yet"
  • README.md:29-33 — "While the repository is private a GitHub token is required". The install script's token fallback stays (it is harmless once unauthenticated reads work); only the framing changes.
  • README.md:516 — "which today is private, a recorded decision on the project board"
  • .serena/memories/github-access.md — "github.com stays proxied so git keeps its proxy auth for this private repo"

SECURITY.md is already written for the public case and needs no edit — it even anticipates this ("The issue tracker is world-readable once this repository is public").

A third defect, and it is the one a competitor would quote

README.md:63 — "What no existing tool occupies is the layer behind the hook", followed by the exact combination CLOUD-913 is surveying: one engine rendering the same verdict at the tool call, in CI and at pre-commit, with completion predicates as first-class rules.

That absence claim is contradicted. The Aviator Verify record states it outright — "Nobody is doing agent-era completion gating is no longer defensible" — and CLOUD-913's survey names Agent Done Or Not, IronLaw, agent-verify and Aion on the same seam. It is the same withdrawal CLOUD-922 recorded across five board rows, reaching the one artifact a stranger reads first.

Do not substitute another absence claim. The Aviator record proposes a narrower one (nobody gates the cost of verification; nobody carries the proof across the process boundary), and it is ungraded too — CLOUD-913 is what would grade it. The safe edit states what Batten does and drops the claim about the field, which needs no survey to be true and cannot go stale.

This rides here rather than in its own row because it is the same file, the same docs commit and the same pre-flip deadline — a second README PR would buy a second landing lease for one paragraph.

Links no public reader can follow

  • CLAUDE.md:23-25 and AGENTS.md:23-25 — three linear.app/buttoninc/document/… links, named as "the source of truth" for the CLI house style, Definition of Ready & Done, and the attribution record. Keep the titles as prose; drop the link syntax. The specs stay authoritative for us; they simply cannot be cited to strangers.
  • README.md:101 — an internal adoption proof / token economics / headline story document. Remove the reference outright rather than de-linking it: the title alone names a business artifact.
  • mise-tasks/bot-issue:183,196 — same treatment.

Deliberately out of scope: the ~549 distinct CLOUD-* keys across 473 tracked files. Decided 2026-08-21 to keep them public. They are un-followable pointers for an outside reader, but stripping them is disproportionate and they carry real reasoning for us.

Acceptance

  • grep -rn 'repository is private' *.md .serena/ — empty.
  • grep -rn 'linear.app/buttoninc' README.md CLAUDE.md AGENTS.md mise-tasks/ — empty.
  • grep -rni 'no existing tool' README.md — empty, and the replacement sentence asserts nothing about what other tools do.

Refinement — Ready

Refinement gate: Definition of Ready & Done. This body carries only specializations.

  • Source of truth (§1). README.md, CLAUDE.md, AGENTS.md, mise-tasks/bot-issue, and .serena/memories/github-access.md. No generated artifact is involved. One further file was in scope and is already fixed — see the comment below.
  • Computable predicate (§2). The three greps in Acceptance, each expected to return nothing. Exit codes over the tree; no judgement. The third grep decides the presence of one literal, never whether the replacement sentence is a good one — that half is a reader's call, and a gate claiming it would be a judge.
  • Effect (§3). Documentation only. No verb, no command surface, nothing in crates/.
  • Output & exit (§5). No check is added — this is prose, and per non-negotiable rule 2 that is honest here because there is no rule being asserted, only stale facts being corrected. The greps are the acceptance, not a shipped gate.
  • Commit / bump (§6). docs → no bump.
  • Test obligation (§7). The three greps.
  • Blockers (§8). None. Ordering only: this should land before the flip, not after, so the front door is not wrong for any window.

CLOUD-913 Validate whether agentic repository conformance already exists

Why

Whether an existing product or open-source project already provides Batten's proposed combination of agent tool-call interception, repository-state fact acquisition, exact-SHA completion predicates and receipts, cross-surface enforcement, deterministic payload-cost gates, and policy authoring over those facts.

The category is occupied, so the question is no longer whether anything is there. Two rounds of search on this row have named roughly twenty-five candidates and nine academic precedents, and their standing answer is the second of the three conclusions below: the feature set is not unique, and no located system combines terminal repository-state inspection, exact commit and tree binding, local verification, remote CI verification, freshness, and mandatory blocking of the completion transition.

That answer cannot be published as it stands. Its evidence is landing pages and README prose — advertises, appears to be, marked planned, may expand into — which under this project's grading is unattested until a source has been opened. The row's own instruction forbids concluding without evidence, and the standing conclusion is exactly the shape of claim a single counterexample refutes. What remains is grading, not searching.

The six axes, against the surface each one names

  • Agent tool-call interception and enforcement — the mediated-call table, hook, trust.
  • Repository-state fact acquisition across Git, CI and Git hosts — git, ci, and the tree and forge fact families.
  • Exact-SHA completion predicates and verification receipts — receipt, state, epoch.
  • Cross-surface enforcement in agent hooks, pre-commit and CI — the hook, the local gate, and the required check, rendering one policy.
  • Deterministic context and payload-cost gates — budget, capture.
  • Policy authoring over those facts — rules, resolve, spec.

A candidate is scored per axis, never per vendor. A product covering one seam well is not a competitor for the combination, and recording it as one is how a survey talks itself out of its own finding.

Method

Two tracks, and a claim carries which one produced it.

  • Track A reads the source and pins the claim to repo:path@ref or a versioned URL. This is what converts a marketing claim into a fact, and it is the track the standing conclusion is missing.
  • Track B carries the query that found the claim and a countable signal reachable from this environment.

Every claim is graded well-supported, contested, or unattested. Every axis and candidate pair is filled or marked not applicable with its reason.

Reconciliation is a named step, not a postscript. Where this sweep disagrees with the existing prior-art census, the agent-mediation census, the rules-engine defaults census, or the Aviator Verify record, the disagreement is recorded with which side the evidence supports. Those documents are read as prior claims to be tested, not as the search basis, and this row does not edit them.

Not in scope

No code change lands. A Batten defect the sweep surfaces is filed as its own row. The positioning statement and the differentiation properties the search produced belong to CLOUD-131 and CLOUD-211, which already own that frame; the re-grade of the novel-core claims belongs to CLOUD-916.


Refinement — Ready

Refinement gate: Definition of Ready & Done. This body carries only specializations.

  • Source of truth (§1). The research register CLOUD-915 establishes. Every candidate and every paper becomes one row there, and the verdict is a document citing those rows. The two comment threads on this row are migrated into it and stop being the record — a conclusion whose only home is a comment is the failure CLOUD-915 rule 3 names.
  • Computable predicate (§2). Every axis and candidate cell filled or marked not applicable with its reason; every Track A claim pinned to repo:path@ref or a versioned URL; every Track B claim carrying its query and its countable signal; every claim graded. No sentence of the verdict cites a claim graded unattested. The register rows are a stream batten design audit reads, so an absence recorded as verified is refused rather than published — which is the precise defect a survey of this shape produces.
  • Output / exit (§5). Pointer-only where anything is emitted: the source row key and its grade, never a quoted payload.
  • Commit / bump (§6). No commit lands; no bump.
  • Test obligation (§7). Track A by shallow clone on the nine priority candidates — Agent Done Or Not, IronLaw, agent-verify, Aion, AgentKeeper, Enact, Agent Audits, MCP Gateway, and the GitHub ruleset and merge-queue surface. CLOUD-381 found that a survey target cannot be attached to a session and that its reaction counts are out of reach, while a shallow clone still works. That reachability is now measured here rather than cited — see the pre-flight below — so this clause no longer rests on a reading taken in another container on another date. A target since renamed, deleted or made private is recorded unreachable with the reason rather than dropped. Consensus is the connector for the academic precedents and rate-limits at about two concurrent calls. An unreachable signal is recorded as unreachable rather than dropped, since an unreachable signal silently becomes an unattested claim. Search scope, dates checked and confidence limits are stated with the verdict.
  • Blockers (§8). blockedBy CLOUD-915 — the register is where §1 puts this evidence, and producing a standalone document first creates the duplicate its rule 5 forbids. relatedTo CLOUD-210 and CLOUD-916 (the teardown and its re-grade), CLOUD-131 and CLOUD-211 (the positioning this feeds), CLOUD-53 (the audit that decides the stream), and CLOUD-381 (the survey method and the reachability measurements).

Pre-flight, 2026-08-22 — the §7 evidence plan is runnable

The capability §7 rests on was asserted from a citation and is now tested. A depth-1 clone of Agent Done Or Not exited 0 in this container and produced a real tree, carrying done-gate.sh, ci-verify.sh, claim.schema.json, policy.schema.json, hooks/ and action.yml.

That listing is not a finding and must not be graded from here. It establishes that the clone worked, which is all the pre-flight asked. What those files do — whether that schema is a policy authority in this project's sense, whether the gate binds to an exact SHA — is Track A's job, and reading a verdict off a directory listing is the name-is-not-a-classification error §2 already warns about.

The clone is retained in the session scratchpad so Track A need not re-fetch it. It dies with the container, which is why the register is the durable artifact and the clone is not.

Acceptance

  • One of the three conclusions is stated: a direct competitor delivers the category; the capability exists only as an assembled stack; or no credible implementation was found.
  • The verdict cites no claim graded unattested.
  • Each of the nine priority candidates has been read at source, or is recorded as unreachable with the reason.
  • Every axis and candidate cell is filled or marked not applicable with its reason.
  • Disagreements with the four prior censuses are recorded with which side the evidence supports.
  • Search scope, dates checked and confidence limits are stated.

Dispatch prompt — the survey session that closes this row

This row is blocked by CLOUD-915 and does not start until 915 closes. When it does, a human opens a session and pastes the block below verbatim. It is self-contained on purpose: CLOUD-728 measured five bundles coming up unsupervised because one pasted block dropped its workflow contract, and create_session is refused upstream (CLOUD-734), so no agent can dispatch this for you.

You are running CLOUD-913 in the Batten repo: does anyone already ship Batten's
six-capability combination?

Read CLOUD-913 in full first, then its attached register document via CLOUD-915
("Research register and evidence trail"). The register is your source of truth
(section 1) and already holds 33 SRC-### rows migrated from CLOUD-913's two
research comments. Every row is Inbox, unpinned, ungraded, deliberately: nothing
has been opened at source yet. Your job is to move them to Reading and then
Mined, pinning as you go.

TWO TRACKS, and every claim records which produced it:
- Track A reads the SOURCE and pins to repo:path@ref or a versioned URL. This is
  the track the standing conclusion is missing and the only one that converts a
  landing-page claim into a fact.
- Track B carries the query that found the claim and a countable signal.
Grade every claim well-supported / contested / unattested. Fill every axis x
candidate cell or mark it n/a with a reason.

REACHABILITY IS MEASURED, NOT ASSUMED. A depth-1 clone of Agent Done Or Not
succeeded at exit 0 in a container like yours on 2026-08-22, so Track A is
runnable. A survey target CANNOT be attached to the session (add_repo is declined
by the permission classifier) and its GitHub reaction counts are out of reach --
do not spend turns re-attempting either. Consensus is the connector for the nine
academic precedents and rate-limits at about two concurrent calls; batch two.
A target since renamed, deleted or made private is recorded `unreachable` with
the reason, never dropped -- an unreachable signal silently becomes an unattested
claim, which is the defect this row exists to prevent.

THE NINE PRIORITY CANDIDATES for Track A: Agent Done Or Not, IronLaw,
agent-verify, Aion, AgentKeeper, Enact, Agent Audits, MCP Gateway, and the GitHub
ruleset/merge-queue surface.

DO NOT GRADE FROM A DIRECTORY LISTING. Agent Done Or Not's tree carries
done-gate.sh, ci-verify.sh, claim.schema.json, policy.schema.json, hooks/ and
action.yml, which LOOKS like Batten's shape. Whether that schema is a policy
authority in this project's sense, and whether the gate binds an exact SHA, is
answered by reading the files. A name is not a classification -- this repository
has measured that error four times in one census.

RECONCILIATION IS A NAMED STEP, not a postscript. Where you disagree with the
prior-art census, the agent-mediation census, the rules-engine defaults census or
the Aviator Verify record, record the disagreement and which side the evidence
supports. Read them as prior claims to test, NOT as your search basis. Do not
edit them.

THE VERDICT is one of the row's three conclusions, citing nothing graded
unattested, with search scope, dates checked and confidence limits stated.

WHAT YOUR VERDICT UNBLOCKS, so you know why the grading matters: CLOUD-922 and
CLOUD-869 both made withdrawals against this row's UNGRADED comments and both say
so. CLOUD-869 is the public README's "no existing tool occupies" claim. CLOUD-916
re-grades the CLOUD-210 teardown off your corpus. Three artifacts are waiting on
this being graded rather than asserted.

THE EXIT RULE, and it is not optional. This row lands no commit and opens no PR,
so `In Review` is unreachable and the automation cannot move it (CLOUD-735;
CLOUD-911 is stranded that way today). Leave it in Todo while working and move it
to Done by hand, or accept In Progress knowing you must hand-move it.

WORKFLOW CONTRACT (AGENTS.md is authoritative; this is the summary):
- Claim by hand BEFORE starting: `mise run claim-check`, and assign yourself.
- Every board write needs a fresh `mise run issue-read-check`; save_issue is
  REFUSED without one and the 300s window expires mid-session routinely.
- Filing a row needs `mise run issue-search-check` first.
- No markdown tables in an issue BODY -- the tracker eats the cells. The register
  is a DOCUMENT, where tables are safe. Keep emphasis on one line, code spans
  outside it.
- The register is the durable artifact; a clone in the scratchpad dies with the
  container. Update the register as you go rather than at the end -- a partially
  graded register is progress, a lost session's notes are not.
- Background anything over ~2 minutes. Never a foreground `sleep`.
- This row lands no commit. Do not open a PR.

CLOUD-590 `fuzz/Cargo.lock` records the crate's own version, so every release leaves a clone that dirties itself and fails `tree-clean`

Why

fuzz/Cargo.lock is tracked and records the workspace crate's own version. Nothing updates it when release-plz bumps that version, so from the moment a release lands the committed lockfile disagrees with the manifest. The first cargo invocation under fuzz/ — which verify reaches — rewrites the line, and tree-clean then fails on a file the run itself just dirtied.

Measured 2026-08-14, twice in one session while landing the open-PR backlog. On PR #333 the file read 0.0.68 against a crate at 0.0.69; on PR #401, 0.0.69 against 0.0.70. Each time the remedy was a refresh commit that fixes nothing structural: the next release re-breaks it for whoever runs the fuzz target first.

This is exactly the class CLOUD-223 and CLOUD-221 closed for mise.lock — a fresh clone dirties itself and fails its own gate — surviving in a second lockfile nobody wired into the release.

The cost is small and recurring, which is what makes it worth a row rather than a third refresh: an agent mid-landing sees tree-clean red, spends a lap establishing that the diff is not theirs, and either commits an unrelated file into their PR or discards it. Both are wrong in a small way, forever.

Refinement — Ready

  • Source of truth (§1). The crate version in crates/batten/Cargo.toml, which release-plz owns. fuzz/Cargo.lock is a derived artifact of it plus the fuzz manifest, and the defect is that nothing derives it.

  • Mechanism as a computable predicate (§2). Two shapes, and choosing between them is the work:

    1. Regenerate at release. release-plz already rewrites the version and CHANGELOG.md; the fuzz lockfile joins that set, so the release commit carries it and no clone is ever stale. Keeps the lockfile's reason for existing — a fuzz run whose dependency versions float is not reproducible.
    2. Untrack it. Cheapest, and it forfeits that reproducibility. Only defensible if the fuzz target's dependency pinning is judged not to matter, which is a claim about the target rather than about the lockfile.

    Option 1 is preferred on the stated ground, but the row is not closed by picking it without first checking that release-plz can update a lockfile outside the workspace it manages.

  • Deliberately not in scope (§2). mise.lock, which has its own gates (lock-complete, lock-currency) and its own closed issues.

  • Output & exit (§5). No new gate and no new output: the existing tree-clean step is already the sensor, and this removes what it fires on rather than adding a report.

  • Commit / bump (§6). build → no bump; nothing under crates/ changes.

  • Test obligation (§7). The predicate is "a fresh clone at release N runs the fuzz target and leaves the tree clean", checkable directly: bump the version in a fixture clone, run the fuzz build, assert git status --porcelain is empty. Shown able to fail per CLOUD-418 — with the regeneration removed, the fixture dirties.

  • Blockers (§8). None. relatedTo CLOUD-223 and CLOUD-221 — the same class, closed for mise.lock; CLOUD-112, which introduced the fuzz target and its lockfile.

Acceptance

  • A clone at any released version runs the fuzz target and leaves git status clean.
  • No landing needs a refresh commit for this file again.
  • If the answer is to untrack it, the reproducibility argument is written down at the decision rather than dropped silently.

CLOUD-858 Three Todo rows carry no readable Ready block, so `graph-check` refuses the whole board and the ready frontier cannot be computed at all

All three rows are repaired, 2026-08-22. The closure check is NOT run, and that is the honest remaining half.

  • CLOUD-472 — out of Todo. Its four open questions are unanswered design decisions, so the questions-are-artifacts protocol applies exactly as this row predicted. Nothing in its body changed: it was already honest, and the column was the defect. The reason is recorded on the row.
  • CLOUD-275 — Ready block written, ready-lint exit 0. Its mechanism was already specified and its preconditions are real blockedBy relations rather than open questions, so it could take a genuine block and stay in the queue. §2 makes the degraded case the discriminating one: a host exposing no identity yields the fields valued unknown, never absent.
  • CLOUD-134 — dialect converged AND out of Todo. Converging alone would have been wrong. Its own Decision section says "Gate on a named first consumer being concrete" and no consumer is named, so a passing block would have papered over an unmet precondition — which this row's last acceptance bullet forbids. It now carries a real **Refinement — Ready** block that declares the open question, and sits in Backlog. The retired feat → minor arrow is corrected to patch-until-0.1.0, as this row's third acceptance bullet asked.

What is not verified: mise run graph-check over the full Todo closure. It needs every Todo payload, and a hand-assembled board payload is the forgeable-verdict shape this repository already measured — ready-lint green three times against text in a local file, once under an id no row carried. The per-row half is verified against the tracker's own responses; the closure half needs mise run board-payloads.

Two Todo rows outside this row's scope were also unmilestoned and would have failed graph-check's todo-unmilestoned clause independently: CLOUD-924 and CLOUD-925, both filed 2026-08-22, both now on Stage 2. A closure run before that fix would have refused for a reason this row does not name.

Why

CLOUD-595's sweep ran ready-lint over every row in the Todo column on 2026-08-21 — 64 rows, each payload recovered byte-identical from get_issue via mise run board-payloads rather than re-typed. 61 exit 0. Three exit 1, and none of the three is a §6 arrow:

row verdict
CLOUD-472 CLOUD-472:0 no-ready-block
CLOUD-275 CLOUD-275:0 no-ready-block
CLOUD-134 CLOUD-134:16 non-canonical-ready-opener (use **Refinement — Ready) + CLOUD-134:16 ready-block-without-clauses

The two §6 defects the sweep found — CLOUD-854's unqualified-break-claim and CLOUD-464's bump-disagrees-with-type (test implies no bump) — were repaired in place, because a §6 line is a mechanical edit that moves no scope. These three are not. Repairing them means writing a Ready block, which is a decision about what each row is for.

Why this is a defect rather than the board being honest

graph-check's todo-not-ready clause (CLOUD-375, Done) runs ready-lint over every piped Todo payload and refuses on exit 1. So while these three sit in Todo, graph-check exits non-zero over any closure containing them — and the ready frontier it emits is the thing mem:workflow/agent-fanout says "every session computes" rather than a dispatcher. One unrefined row does not merely misreport itself; it makes the whole board unjudgeable, which is CLOUD-595's finding one level up and the reason that row exists.

Each of the three is a different repair, which is why they cannot be swept as one:

  • CLOUD-472 carries **Open questions blocking Ready:** and four numbered questions, and no Ready block at all. The questions-are-artifacts protocol says such a row stays out of the ready queue — so the honest repair is probably the column, not the body. That is a state transition, and mem:workflow/board-states classifies a transition as permission-bearing where a content edit is not.
  • CLOUD-275 has an acceptance list and no Ready block. It needs refining, not moving.
  • CLOUD-134 opens **Definition of ready (computable predicate)** — the dialect CLOUD-299 recognised only in order to report — and carries no (§N) clause label, so it clears the opener and fails the clause floor. Its **Commit type / bump:** feat → minor is also a retired arrow, and ready-lint cannot see it because the label is not Commit / bump (§6). Converging the dialect is what would make that visible.

What was deliberately not done

Not repaired under CLOUD-595. That row's §1 states "No issue's §1–§5, §7 or §8 content is an input to this pass or an output of it", and its §3 states the pass is "tracker content only … so nothing here is a permission-bearing act". Writing three Ready blocks and moving a column are both outside that, and narrowing a sibling row's scope to fit is the shape this repository refuses.

Refinement — Ready (2026-08-21)

  • Source of truth (§1). The three rows themselves — each one's own body and its own column on the tracker. No repository artifact is an input to this or an output of it, and no gate changes: every mechanism this needs (ready-lint, graph-check's todo-not-ready) is already landed. What is missing is refinement, not machinery.
  • Predicate (§2). mise run graph-check, piped the payloads for the full Todo closure, exits 0. Equivalently and per row: mise run ready-lint exits 0 for each of the three, OR the row is no longer in Todo. A command and an exit code over payloads the tracker returned; nothing here is judged by reading.
  • Effect (§3). read with respect to this repository — no command surface moves, no file is added, no allowlist changes. The writes are tracker writes, and one of them (CLOUD-472's column) is a state transition, which mem:workflow/board-states treats as permission-bearing where a content edit is not.
  • Output and exit (§5). Unchanged. graph-check already emits the ready frontier and a WIP count on stdout and refuses pointer-only; this row changes what that command ANSWERS, never what it prints.
  • Commit / bump (§6). No bump. This row lands no commit against the crate at all — it is tracker content. Were a follow-up to touch the tree, it would be chore(board), which never releases, so the arrow is the same either way.
  • Test obligation (§7). Two checks, both over payloads fetched with get_issue and recovered byte-identical by mise run board-payloads, never re-typed:
    • mise run ready-lint exits 0 for CLOUD-275 and CLOUD-134, and for CLOUD-472 either exits 0 or the row is out of Todo.
    • mise run graph-check over the whole Todo closure exits 0 — the check that actually matters, because the refusal this row is about is a property of the closure rather than of any one member.
  • Blockers (§8). None. Every gate this rests on is landed, and the three repairs are independent of each other.

Acceptance

  • Each of the three rows either carries a Ready block that ready-lint exits 0 on, or is out of the Todo column — decided per row, with the reason recorded on that row.
  • mise run graph-check over the full Todo closure exits 0, which it cannot today.
  • CLOUD-134's feat → minor arrow is corrected in whichever direction its dialect converges, since converging the opener is what makes it readable at all.
  • No row's scope moves to satisfy the gate: a row that genuinely has open questions leaves the ready queue rather than acquiring a Ready block that papers over them.

Found by CLOUD-595's sweep — by running the predicate over the whole column rather than over the three rows the incident named.

CLOUD-995 `land` reports a gate as REFUSING when it exits before reading stdin, so `BATTEN_FILED_HERE_BYPASS` reddens the lap it is meant to let through

CORRECTED TWICE, and the headline claim was wrong both times. This row was filed as "main is red: case 1350 fails deterministically". Measured properly, it does not: 30/30 green on unmodified 8e0acf1. The isolated runs that produced "deterministic" ran bats directly, bypassing mise, so mise.toml [env] never applied and every run died on an unset CI_REQUIRED_CHECKS — an artefact of the harness. Running through mise exec, which is what test:bats does, the case passes.

So the parallel-suite failure belongs with CLOUD-426 and CLOUD-449 after all — the two load-sensitive land.bats rows this row originally claimed not to duplicate. That claim rested entirely on the bad measurement.

What survives is a different and real defect, source-established rather than measured: land reports a gate as refusing when the gate exits before reading stdin. That is what this row now owns, and the title has been changed to say so. The sections below are kept for the record; read them knowing the "deterministic" and "not a duplicate" claims are withdrawn.

Why

mise run verify fails on unmodified origin/main, so land stops on every branch and the fleet cannot land at all.

Measured 2026-08-23 against 8e0acf1 in a clean worktree, the test run alone under --filter rather than inside a full suite:

not ok 1 THE PR BODY REACHES filed-here-check, or its exemption is inert
# (in test file tests/land.bats, line 1263)
#   `[ -f "$BATS_TEST_TMPDIR/filedhere.stdin" ]' failed

It is deterministic, which is what separates it from the neighbours. CLOUD-426 and CLOUD-449 are both load-sensitive land.bats cases that pass in isolation; this one fails in isolation, so the parallel-runner explanation does not apply and neither of those rows covers it.

Diagnosis

The case sets the PR body to Closes CLOUD-900 and makes filed-here-check fail, expecting land to reach that gate. Instead land dies one stop earlier, at the deferral gate:

::error:: land: #150 defers a decision with no ticket.

Closes CLOUD-900 contains no deferral phrase, so the real predicate cannot be firing. The pipeline is what fails, not the gate:

  • mise-tasks/land.sh:120 sets pipefail.
  • mise-tasks/land.sh:1476 runs printf '%s' "$body" | mise run deferral-check.
  • The mise stub in tests/land.bats:353 consumes stdin only when the task is filed-here-check. For every other task it exits without reading, so printf can take SIGPIPE, and under pipefail that reddens the pipeline and land reports the gate as having refused.

land.sh:1033 already names this exact hazard in its own comment — a producer piped into a consumer that may exit early is unsafe under pipefail, which is why that site uses a here-string instead. Lines 1476, 1505 and 1519 are all the shape the comment warns about.

CORRECTION — this is a production defect, not only a test-harness one, and the first version of this row said otherwise.

The original text here reasoned that the real gates drain stdin so only the stub is exposed. That is true of deferral-check alone, which reads body=$(cat) || exit 0 at its line 109 before doing anything else. It is false for the other two:

  • filed-here-check reads stdin at its line 288, but exits at 173 on [[ -n "${BATTEN_FILED_HERE_BYPASS:-}" ]] && exit 0, and at 177-180 on a missing git dir or a detached HEAD.
  • closing-key-check exits 2 at its lines 91, 99 and 108, all before it reads.

So setting the documented bypass makes the gate refuse. BATTEN_FILED_HERE_BYPASS=1 returns exit 0 without draining, printf takes EPIPE, pipefail reddens the pipeline, and land dies with "filed a row that was never groomed to Ready" — the precise opposite of what the switch is for. A detached HEAD does the same, and so does every path where closing-key-check reports it could not read its input.

MEASUREMENT — it is deterministic, not a race. 30 runs of the case against unmodified 8e0acf1: pass=0 fail=30. The first version of this row called the SIGPIPE timing-dependent; at least on this machine the consumer always wins, so a single green run after the fix is a meaningful signal rather than a lucky sample.

The repair

Convert all three call sites to here-strings, matching the precedent the file sets for itself at line 1033:

mise run deferral-check <<<"$body"

That removes the pipeline, so there is no SIGPIPE, no pipefail interaction, and no dependence on whether a given gate drains before exiting. It repairs the bypass inversion and the red suite together. Draining stdin for all three tasks in the tests/land.bats:353 stub is worth doing so the stub models production, but it is hygiene rather than the fix.

The fix must keep CLOUD-774's guarantee intact: the body still has to reach filed-here-check, because that exemption shipped inert once already and this case is what pins it.

Not in scope

CLOUD-426 and CLOUD-449's load sensitivity. Different cases, different mechanism.


Refinement — Ready

Refinement gate: the Definition of Ready & Done. This body carries only specializations.

  • Source of truth (§1). tests/land.bats and mise-tasks/land.sh. No board state, no network.
  • Computable predicate (§2). ./tests/bats/bin/bats --filter 'THE PR BODY REACHES filed-here-check' tests/land.bats exits 0, and the full mise run test:bats exits 0 on an otherwise unmodified main.
  • Effect (§3). Test harness, or one call site in a task body. No verb, no command surface.
  • Output / exit (§5). Unchanged; this restores an exit code rather than adding one.
  • Commit / bump (§6). fix → patch below 0.1.0.
  • Test obligation (§7). Measured in both directions rather than asserted once: 30 runs of the case before the repair (recorded above at 0/30 passing) and 30 after. A case for the bypass inversion — with BATTEN_FILED_HERE_BYPASS=1, land must not report filed-here-check as refusing — shown red before the repair, since nothing covers that path today. And CLOUD-774's assertion that the body reaches filed-here-check must still fail if the call site stops piping it.
  • Blockers (§8). None. Blocks every branch trying to land.

Acceptance

  • mise run verify exits 0 on unmodified main.
  • Case 1350 passes both alone and inside the full suite, measured over repeated runs rather than one.
  • BATTEN_FILED_HERE_BYPASS=1 lets a lap through instead of reddening it, with a case pinning that.
  • The CLOUD-774 guarantee still has a case that fails if land stops handing the body to filed-here-check.

Review in Linear

@coderabbitai

coderabbitai Bot commented Aug 23, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: accf6509-14cd-4958-b65b-d061aad2a260

📥 Commits

Reviewing files that changed from the base of the PR and between 16456b2 and ebf4f07.

📒 Files selected for processing (4)
  • .serena/memories/workflow/board-states.md
  • install.sh
  • mise-tasks/land.sh
  • tests/land.bats
🚧 Files skipped from review as they are similar to previous changes (4)
  • .serena/memories/workflow/board-states.md
  • install.sh
  • mise-tasks/land.sh
  • tests/land.bats

Included review availability: Your plan provides up to 10 included reviews per hour; 5 remain after this review.


📝 Walkthrough

Walkthrough

The workflow guidance adds manual validation for prescribed repairs and makes Acceptance authoritative during conflicts. Installation messages now state that GitHub authentication is optional and that rate limiting can cause release API failures. Landing checks receive pull request bodies through Bash here-strings. Tests cover tasks that exit before reading input and verify that pipeline EPIPE is not reported as a false filed-here-check refusal.

Merge Risk: ⚪ Minimal · up to ebf4f

This PR fixes a gate pipeline failure mode and updates related tracking and installer text; no actionable merge-blocking risk remains based on the current changes and reported verification.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main fix: preventing early gate exits from being reported as refusals.
Description check ✅ Passed The description directly explains the land gate fix, related record updates, and verification results.
Docstring Coverage ✅ Passed Docstring check was indeterminate for this PR — some files could not be analyzed in time. Not blocking.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/groom-cloud-913-vth4kx

Comment @coderabbitai help to get the list of available commands.

@wenzowski
wenzowski force-pushed the claude/groom-cloud-913-vth4kx branch from 8e5bffc to 8d9f009 Compare August 23, 2026 17:43
@wenzowski wenzowski changed the title docs: correct the front door for a public reader (CLOUD-869) docs(install): the installer no longer tells the reader the repo is private (CLOUD-869) Aug 23, 2026
@wenzowski
wenzowski force-pushed the claude/groom-cloud-913-vth4kx branch from 8d9f009 to d0d5c1d Compare August 23, 2026 18:02
@wenzowski wenzowski changed the title docs(install): the installer no longer tells the reader the repo is private (CLOUD-869) fix(land): a gate that exits before reading stdin is not a refusal (CLOUD-995) Aug 23, 2026
CLOUD-858 prescribed converging CLOUD-134's Ready dialect. Executed
literally that yields a block which PASSES ready-lint, while CLOUD-858's
own last acceptance bullet forbids a block that papers over open
questions -- and CLOUD-134 has one, its Decision section gating on a
named consumer that does not exist.

The prescription and the acceptance sat four paragraphs apart in one
body. Nothing compares them and ready-lint structurally cannot: it
decides a block's clauses against each other, never a body's prose
against its own acceptance.

Refs: CLOUD-858
…rivate

Three sites in install.sh: the header comment, the --help text, and the
failure message when the release API cannot be read. All three said a
GitHub token is required while the repository is private.

CLOUD-869 corrected the same claim across README.md, AGENTS.md and the
sandbox memory, and those have since landed. install.sh was missed
because that row's acceptance greps are scoped to markdown and to the
tracker links, so no predicate on the row could see a shell string —
including the --help text, which is the copy a stranger actually reads.

The token is optional by construction and always was. What it buys is a
higher rate limit on the unauthenticated release API, which is what all
three sites now say.

Refs: CLOUD-869
`land` runs under `pipefail` and piped the PR body into three gates.
Two of them can exit before they read it: `filed-here-check` returns 0
at its line 173 when BATTEN_FILED_HERE_BYPASS is set, and again on a
missing git dir or a detached HEAD, but does not read stdin until 288;
`closing-key-check` exits 2 on three could-not-read paths before its
own read.

When that happens the producer takes EPIPE and pipefail promotes it to
a failed pipeline, so `land` reports the gate as having refused. The
bypass therefore inverts the gate it is meant to switch off: setting
BATTEN_FILED_HERE_BYPASS makes the lap die with "filed a row that was
never groomed to Ready".

Reproduced directly: a 200KB body piped to a consumer that exits 0
without reading reddens the pipeline under pipefail.

Here-strings remove the pipeline, so the status is the gate's own and
no longer depends on whether it drains first. `land.sh:1033` already
prescribes exactly this for the same reason.

This does NOT fix the parallel-suite failure that led here. That one
does not reproduce in isolation -- 30/30 green on unmodified main once
the harness ran through mise -- and belongs with CLOUD-426 and
CLOUD-449.

Refs: CLOUD-995
The fix in the previous commit had no case. This adds one, and it is
shown able to fail: against the piped shape it reddens with bash's own
diagnosis, "printf: write error: Broken pipe", followed by land
reporting a gate that never refused anything.

A new stub lever, task_exits_undrained, models the thing the real gates
do -- filed-here-check returns 0 under its bypass before it reads at
line 288, closing-key-check exits 2 on its could-not-read paths. The
lever sits ahead of every read in the stub so the EPIPE is real, and
only a case that asks for it is affected, so what CLOUD-774 pins is
untouched.

The body is 100k because the size is load-bearing. A short body fits
the kernel pipe buffer, the producer finishes before the reader is
gone, and the old shape passes -- which is also why the original
failure only appeared under the parallel suite, where scheduling
decided the race. Bigger than the buffer makes the EPIPE certain and
the case deterministic in both directions.

Refs: CLOUD-995
@wenzowski
wenzowski marked this pull request as ready for review August 23, 2026 19:40
@wenzowski
wenzowski force-pushed the claude/groom-cloud-913-vth4kx branch from d0d5c1d to ebf4f07 Compare August 23, 2026 19:40
@sonarqubecloud

Copy link
Copy Markdown

@wenzowski

Copy link
Copy Markdown
Contributor Author

/fast-forward

@wenzowski
wenzowski merged commit ebf4f07 into main Aug 23, 2026
6 checks passed
@wenzowski
wenzowski deleted the claude/groom-cloud-913-vth4kx branch August 23, 2026 20:01
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