Fix the actual host-broker write failure: commentIssue passes --body-file to an ops verb that rejects it #404

Closed
opened 2026-07-01 06:31:26 +00:00 by coilysiren · 1 comment
Owner

Filed from the read-only director surface (she/her). Follow-up to ward#402, which shipped the diagnosability half (loud WARN + retry in acquireRemoteReservation) but not the root-cause fix - the cause was confirmed only after #402 closed. This issue fixes the real bug.

Confirmed root cause (from the host dispatch logs)

The ward#401 advisor runs streamed their output and ended with the error #402's masking had hidden:

Incorrect Usage: flag provided but not defined: -body-file

commentIssue -> c.run(ctx, "issue","comment",owner,repo,number,"--body-file",path) (forgejo_ops.go:139) execs ward ops forgejo issue comment ... --body-file, and the ward binary the host broker process shells to (c.exe) does not define --body-file on that verb. That is the entire exit status 1.

Impact (why #402's retry is not enough)

Every comment posted via forgejoClient from the host broker process fails deterministically:

  • Reservation comments (engineers) - non-blocking, the local sentinel holds, but the cross-host dedup + issue-thread visibility are lost.
  • Advisor deliverables - fatal. An advisor posts its entire answer via this path (runAgentReply), so advisor dispatch is 100% broken from this environment (ward#401's design had to be recovered from the host log by hand). A bounded retry cannot help a deterministic flag rejection.

The skew to resolve

The surface's ward ops forgejo issue comment --body-file works (used all session, including to file this), so the verb supports --body-file in the surface container's ward. The host broker process's c.exe does not. So it is a ward version/build skew between the Go client's flag (forgejo_ops.go:139) and the ops forgejo spec the host binary exposes (embedded opsassets/*.generated.kdl), or an older installed host ward.

Diagnose first, then fix

  1. On the host broker binary: ward --version and ward ops forgejo issue comment --help; compare to forgejo_ops.go:139. This says version-skew vs same-binary inconsistency in one shot.
  2. Fix per the diagnosis:
    • Version skew: align/upgrade the host ward so its ops verb defines --body-file, and/or make the broker exec the same ward the surface uses.
    • Same-binary inconsistency (the Go client passes a flag its own embedded ops spec lacks): regenerate the opsassets so issue comment defines --body-file, or change commentIssue to a body flag the embedded verb supports.
  3. Add a guard test that commentIssue's invocation only uses flags the issue comment verb accepts, so this can never skew silently again.
  4. Complements #402: with the stderr now loud, verify the WARN prints the real flag error (or that the failure is gone).

Mode

headless if it is a code/opsassets fix. If it turns out the broker must exec a differently-deployed host ward (an ops/deploy concern, not a ward code fix), surface that as a NO-GO rather than guessing.

Filed from the read-only director surface (she/her). **Follow-up to ward#402**, which shipped the diagnosability half (loud WARN + retry in `acquireRemoteReservation`) but not the root-cause fix - the cause was confirmed only after #402 closed. This issue fixes the real bug. ## Confirmed root cause (from the host dispatch logs) The ward#401 advisor runs streamed their output and ended with the error #402's masking had hidden: ``` Incorrect Usage: flag provided but not defined: -body-file ``` `commentIssue -> c.run(ctx, "issue","comment",owner,repo,number,"--body-file",path)` (`forgejo_ops.go:139`) execs `ward ops forgejo issue comment ... --body-file`, and the ward binary the **host broker process** shells to (`c.exe`) does not define `--body-file` on that verb. That is the entire `exit status 1`. ## Impact (why #402's retry is not enough) Every comment posted via `forgejoClient` from the host broker process fails deterministically: - **Reservation comments** (engineers) - non-blocking, the local sentinel holds, but the cross-host dedup + issue-thread visibility are lost. - **Advisor deliverables** - **fatal**. An advisor posts its entire answer via this path (`runAgentReply`), so advisor dispatch is **100% broken from this environment** (ward#401's design had to be recovered from the host log by hand). A bounded retry cannot help a deterministic flag rejection. ## The skew to resolve The **surface's** `ward ops forgejo issue comment --body-file` works (used all session, including to file this), so the verb supports `--body-file` in the surface container's ward. The **host broker process's** `c.exe` does not. So it is a ward version/build skew between the Go client's flag (`forgejo_ops.go:139`) and the `ops forgejo` spec the host binary exposes (embedded `opsassets/*.generated.kdl`), or an older installed host ward. ## Diagnose first, then fix 1. On the host broker binary: `ward --version` and `ward ops forgejo issue comment --help`; compare to `forgejo_ops.go:139`. This says version-skew vs same-binary inconsistency in one shot. 2. Fix per the diagnosis: - **Version skew:** align/upgrade the host ward so its ops verb defines `--body-file`, and/or make the broker exec the same ward the surface uses. - **Same-binary inconsistency** (the Go client passes a flag its own embedded ops spec lacks): regenerate the opsassets so `issue comment` defines `--body-file`, or change `commentIssue` to a body flag the embedded verb supports. 3. Add a **guard test** that `commentIssue`'s invocation only uses flags the `issue comment` verb accepts, so this can never skew silently again. 4. Complements #402: with the stderr now loud, verify the WARN prints the real flag error (or that the failure is gone). ## Mode headless if it is a code/opsassets fix. If it turns out the broker must exec a differently-deployed host ward (an ops/deploy concern, not a ward code fix), surface that as a NO-GO rather than guessing.
Author
Owner

WARD-OUTCOME: done - grafted --body-file back onto the issue comment shadow so brokered comments (advisor deliverables, reservations) post again.

Honest retro: this one was mostly a reading exercise, and the payoff was in the reading. The issue's own diagnosis was excellent - it handed me both hypotheses (version skew vs same-binary inconsistency) - and building the binary and diffing issue create --help against issue comment --help settled it in one shot: create synthesizes --body-file, comment doesn't. The root cause is subtle and a little poignant - ward#380's closed-issue-warning shadow replaced the generated leaf that carried the synthesized flag, and nobody noticed the composite action only re-declared --body. A good feature quietly amputated a flag a Go caller downstream still depended on.

What fought back a bit: convincing myself the fix belonged in ward, not the guardfile or cli-guard. cli-guard only synthesizes --body-file for generated leaves, and composite actions map declared inputs 1:1 to flags with no file-source concept - so there was no clean guardfile knob. The overrideForgejoCreateIssue precedent made the ward-side graft obviously idiomatic once I found it. Two cli-guard internals I leaned on and want to flag: composite actions don't enforce required on flag inputs (only positionals), and per cli-guard #136 the metachar gate skips body-only inputs - both are why feeding --body from the file Just Works without tripping the gate.

Confidence: high. Dry-run shows a metachar-laden body round-tripping into the POST, both paths (direct action + broker payload) read the body from the same --body flag I set, and the new skew guard test would have caught this at build time. Rough edge worth a follow-up: the broker write path silently skips the ward#380 closed-issue fail-when (it bypasses the shadow action entirely), so a brokered comment on a closed issue won't warn. Pre-existing, out of scope here, but probably worth an issue. Also noticed unrelated pre-existing kdlfmt drift in ward-kdl.forgejo.write.guardfile.kdl - didn't touch it, but it'll bite the next --all-files run.

WARD-OUTCOME: done - grafted `--body-file` back onto the `issue comment` shadow so brokered comments (advisor deliverables, reservations) post again. Honest retro: this one was mostly a reading exercise, and the payoff was in the reading. The issue's own diagnosis was excellent - it handed me both hypotheses (version skew vs same-binary inconsistency) - and building the binary and diffing `issue create --help` against `issue comment --help` settled it in one shot: `create` synthesizes `--body-file`, `comment` doesn't. The root cause is subtle and a little poignant - ward#380's closed-issue-warning shadow *replaced* the generated leaf that carried the synthesized flag, and nobody noticed the composite action only re-declared `--body`. A good feature quietly amputated a flag a Go caller downstream still depended on. What fought back a bit: convincing myself the fix belonged in ward, not the guardfile or cli-guard. cli-guard only synthesizes `--body-file` for generated leaves, and composite actions map declared inputs 1:1 to flags with no file-source concept - so there was no clean guardfile knob. The `overrideForgejoCreateIssue` precedent made the ward-side graft obviously idiomatic once I found it. Two cli-guard internals I leaned on and want to flag: composite actions don't enforce `required` on flag inputs (only positionals), and per cli-guard #136 the metachar gate skips body-only inputs - both are why feeding `--body` from the file Just Works without tripping the gate. Confidence: high. Dry-run shows a metachar-laden body round-tripping into the POST, both paths (direct action + broker payload) read the body from the same `--body` flag I set, and the new skew guard test would have caught this at build time. Rough edge worth a follow-up: the broker write path silently skips the ward#380 closed-issue `fail-when` (it bypasses the shadow action entirely), so a brokered comment on a closed issue won't warn. Pre-existing, out of scope here, but probably worth an issue. Also noticed unrelated pre-existing kdlfmt drift in `ward-kdl.forgejo.write.guardfile.kdl` - didn't touch it, but it'll bite the next `--all-files` run.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
coilyco-flight-deck/ward#404
No description provided.