ward doctor's Makefile-target check false-positives on a textbook-standard Makefile #480

Closed
opened 2026-07-01 22:51:17 +00:00 by coilyco-ops · 7 comments
Member

Why

Persona: the-agent-itself, angle first-run (behavioral, gated - approved). Built a scratch toy repo (never committed to any remote) with a minimal, completely standard Makefile:

build:
	@echo "building"
test:
	@echo "testing"
install:
	@echo "installing"

Verified make build / make test / make install all run correctly - real tabs, real targets, no typos (checked with od -c).

Declared them in .ward/ward.yaml per READMEs own example shape:

commands:
  build: {run: make build, description: Build.}
  test: {run: make test, description: Test.}
  install: {run: make install, description: Install.}

ward doctor in that repo:

ward doctor security: no security: declared
ward: allowlist: .ward/ward.yaml:4: commands.build has no matching Makefile target
.ward/ward.yaml:7: commands.test has no matching Makefile target
.ward/ward.yaml:10: commands.install has no matching Makefile target

All three - every command declared - are reported as having no matching Makefile target, despite the Makefile plainly containing exactly those three targets and make <target> working for all three by hand. This is the exact contract READMEs "What it does" section names as the products core mechanism: "Each repo declares which Makefile targets are exposed in .ward/ward.yaml. The contract is verified by..." (originally ward lint, see the companion issue - the check now appears to live in ward doctors allowlist probe). On a fresh install, following the documented steps precisely, that verification is false-positive on a valid config.

Deliverable

Fix the Makefile-target matcher (upstream in cli-guard/allowlist per doctor.md) so it correctly recognizes standard target:\n\trecipe rules, or if the matcher has stricter requirements (.PHONY, a specific comment format, etc.) document them - today README/FEATURES.md/AGENTS.md give zero guidance on what counts as a "matching" target beyond the name.

Done condition

ward doctor (or ward lint, once restored) reports a clean allowlist match for a .ward/ward.yaml whose commands.<x>.run targets exist in the repos Makefile with standard syntax.


Severity: adoption-fatal · persona: the-agent-itself · angle: first-run
Filed by Claude Code during a cold-read release pressure test (run 17). Anchor: ward#201 (blind agent pressure testing).

## Why Persona: `the-agent-itself`, angle `first-run` (behavioral, gated - approved). Built a scratch toy repo (never committed to any remote) with a minimal, completely standard Makefile: ```makefile build: @echo "building" test: @echo "testing" install: @echo "installing" ``` Verified `make build` / `make test` / `make install` all run correctly - real tabs, real targets, no typos (checked with `od -c`). Declared them in `.ward/ward.yaml` per READMEs own example shape: ```yaml commands: build: {run: make build, description: Build.} test: {run: make test, description: Test.} install: {run: make install, description: Install.} ``` `ward doctor` in that repo: ``` ward doctor security: no security: declared ward: allowlist: .ward/ward.yaml:4: commands.build has no matching Makefile target .ward/ward.yaml:7: commands.test has no matching Makefile target .ward/ward.yaml:10: commands.install has no matching Makefile target ``` All three - every command declared - are reported as having no matching Makefile target, despite the Makefile plainly containing exactly those three targets and `make <target>` working for all three by hand. This is the exact contract READMEs "What it does" section names as the products core mechanism: "Each repo declares which Makefile targets are exposed in `.ward/ward.yaml`. The contract is verified by..." (originally `ward lint`, see the companion issue - the check now appears to live in `ward doctor`s allowlist probe). On a fresh install, following the documented steps precisely, that verification is false-positive on a valid config. ## Deliverable Fix the Makefile-target matcher (upstream in `cli-guard/allowlist` per `doctor.md`) so it correctly recognizes standard `target:\n\trecipe` rules, or if the matcher has stricter requirements (`.PHONY`, a specific comment format, etc.) document them - today README/FEATURES.md/AGENTS.md give zero guidance on what counts as a "matching" target beyond the name. ## Done condition `ward doctor` (or `ward lint`, once restored) reports a clean allowlist match for a `.ward/ward.yaml` whose `commands.<x>.run` targets exist in the repos Makefile with standard syntax. --- **Severity: adoption-fatal** · persona: the-agent-itself · angle: first-run Filed by Claude Code during a cold-read release pressure test (run 17). Anchor: ward#201 (blind agent pressure testing).
coilyco-ops added this to the ward launch milestone 2026-07-01 23:29:24 +00:00
Author
Member

Note from triage: #478 (implement ward lint) was decided as implement-not-dedocument, and the same Makefile-target matching layer likely underlies both tickets. Whoever picks either up should check the other's repro in the same PR. Recorded by Claude Code (Fable) during the 2026-07-01 ward launch triage session with Kai.

Note from triage: #478 (implement ward lint) was decided as implement-not-dedocument, and the same Makefile-target matching layer likely underlies both tickets. Whoever picks either up should check the other's repro in the same PR. Recorded by Claude Code (Fable) during the 2026-07-01 ward launch triage session with Kai.
Author
Member

🔒 Reserved by ward agent --driver claude — container engineer-claude-ward-480 on host kais-macbook-pro-2.local is carrying this issue (reserved 2026-07-02T02:21:45Z). Concurrent ward agent runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); --force overrides.

— Claude (she/her), via ward agent

<!-- ward-agent-reservation --> 🔒 Reserved by `ward agent --driver claude` — container `engineer-claude-ward-480` on host `kais-macbook-pro-2.local` is carrying this issue (reserved 2026-07-02T02:21:45Z). Concurrent `ward agent` runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); `--force` overrides. <!-- ward-agent-signature --> — Claude (she/her), via `ward agent`
Owner

WARD-OUTCOME: done - ward doctor now explains the allowlist target-match contract on failure, and README/doctor.md/FEATURES.md document it (the check no longer reads as a false positive).

Honest retro: this one turned out to be a scoping puzzle more than a hard bug. The reproduction was instant - a bare build:\n\trecipe target with no ## ... help comment is never registered, so it reports "no matching Makefile target" even though make build runs. The surprise was that the strictness is intentional: the matcher is an anti-drift guard (target needs a ## <description> comment whose text equals the yaml description:), it's dogfooded by ward's own Makefile, and it's tested upstream in cli-guard/allowlist. So "recognize plain targets" isn't a clean fix - it would delete the description-drift check.

What fought back: the actual matcher lives upstream in cli-guard, which this container isn't granted to push. So I couldn't land the done-condition's literal reading (a comment-less Makefile passing) - that needs a cli-guard change. I took the deliverable's explicit alternative: make it legible instead of silent. ward doctor now appends a one-line contract hint to every allowlist failure, and README (which wrongly said ward lint verifies this and gave zero guidance), doctor.md, and FEATURES.md now spell out the three requirements. Fully landable in ward, and it kills the "why is my valid target rejected?" experience that made this adoption-fatal.

Confidence: high on what landed (green tests + a new render test lock the hint; pre-commit clean; pushed to main). Lower on whether this is the whole answer - if the call is that the ## comment requirement is itself too strict for cold-read adoption, that's an upstream cli-guard matcher change I can't make from here.

Follow-ups worth filing: (1) an upstream cli-guard issue to decide whether a bare target should match by name (relax) or the strictness stays (keep + this doc is the fix); (2) cross-link with #478 per triage - that ticket's "implement ward lint, not de-document" direction rides the same matcher layer, and my README edit corrected the stale ward lint claim, so whoever restores ward lint should re-check this repro in the same PR.

— Claude (she/her), via ward agent

WARD-OUTCOME: done - `ward doctor` now explains the allowlist target-match contract on failure, and README/doctor.md/FEATURES.md document it (the check no longer reads as a false positive). Honest retro: this one turned out to be a scoping puzzle more than a hard bug. The reproduction was instant - a bare `build:\n\trecipe` target with no `## ...` help comment is never registered, so it reports "no matching Makefile target" even though `make build` runs. The surprise was that the strictness is *intentional*: the matcher is an anti-drift guard (target needs a `## <description>` comment whose text equals the yaml `description:`), it's dogfooded by ward's own Makefile, and it's tested upstream in `cli-guard/allowlist`. So "recognize plain targets" isn't a clean fix - it would delete the description-drift check. What fought back: the actual matcher lives upstream in cli-guard, which this container isn't granted to push. So I couldn't land the done-condition's literal reading (a comment-less Makefile passing) - that needs a cli-guard change. I took the deliverable's explicit alternative: make it legible instead of silent. `ward doctor` now appends a one-line contract hint to every allowlist failure, and README (which wrongly said `ward lint` verifies this and gave zero guidance), doctor.md, and FEATURES.md now spell out the three requirements. Fully landable in ward, and it kills the "why is my valid target rejected?" experience that made this adoption-fatal. Confidence: high on what landed (green tests + a new render test lock the hint; pre-commit clean; pushed to main). Lower on whether this is the *whole* answer - if the call is that the `## comment` requirement is itself too strict for cold-read adoption, that's an upstream cli-guard matcher change I can't make from here. Follow-ups worth filing: (1) an upstream cli-guard issue to decide whether a bare target should match by name (relax) or the strictness stays (keep + this doc is the fix); (2) cross-link with #478 per triage - that ticket's "implement `ward lint`, not de-document" direction rides the same matcher layer, and my README edit corrected the stale `ward lint` claim, so whoever restores `ward lint` should re-check this repro in the same PR. — Claude (she/her), via `ward agent`
Author
Member

🔒 Reserved by ward agent --driver claude — container engineer-claude-ward-480 on host kais-macbook-pro-2.local is carrying this issue (reserved 2026-07-02T08:13:37Z). Concurrent ward agent runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); --force overrides.

— Claude (she/her), via ward agent

<!-- ward-agent-reservation --> 🔒 Reserved by `ward agent --driver claude` — container `engineer-claude-ward-480` on host `kais-macbook-pro-2.local` is carrying this issue (reserved 2026-07-02T08:13:37Z). Concurrent `ward agent` runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); `--force` overrides. <!-- ward-agent-signature --> — Claude (she/her), via `ward agent`
Author
Member

🔒 Reserved by ward agent --driver claude — container engineer-claude-ward-480 on host kais-macbook-pro-2.local is carrying this issue (reserved 2026-07-02T08:13:37Z). Concurrent ward agent runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); --force overrides.

— Claude (she/her), via ward agent

<!-- ward-agent-reservation --> 🔒 Reserved by `ward agent --driver claude` — container `engineer-claude-ward-480` on host `kais-macbook-pro-2.local` is carrying this issue (reserved 2026-07-02T08:13:37Z). Concurrent `ward agent` runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); `--force` overrides. <!-- ward-agent-signature --> — Claude (she/her), via `ward agent`
Author
Member

🔒 Reserved by ward agent --driver claude — container engineer-claude-ward-480 on host kais-macbook-pro-2.local is carrying this issue (reserved 2026-07-02T08:13:37Z). Concurrent ward agent runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); --force overrides.

— Claude (she/her), via ward agent

<!-- ward-agent-reservation --> 🔒 Reserved by `ward agent --driver claude` — container `engineer-claude-ward-480` on host `kais-macbook-pro-2.local` is carrying this issue (reserved 2026-07-02T08:13:37Z). Concurrent `ward agent` runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); `--force` overrides. <!-- ward-agent-signature --> — Claude (she/her), via `ward agent`
Owner

WARD-OUTCOME: done - AGENTS.md now names the allowlist target-match contract (the one doc surface the #480 fix skipped); the matcher-relax decision stays upstream in cli-guard where it can actually be made.

Honest retro: I came in expecting a bug and found a mostly-finished issue. The prior #480 run (ward 21d043f) had already landed the real experience fix - a contract hint on every allowlist failure plus README/doctor.md/FEATURES.md spelling out the three requirements - and closed the ticket. So my job narrowed to the one gap that fix left: the deliverable explicitly named AGENTS.md as giving zero guidance, and AGENTS.md was the one doc surface 21d043f never touched. I reproduced the false positive first (bare build:/test:/install: Makefile, hand-written yaml, three "no matching Makefile target" lines), confirmed the hint now renders, then added the contract to the adopter's Validate step.

What fought back is the same wall the first run hit: the matcher lives upstream in cli-guard/allowlist, which this container only sees read-only under /substrate - not granted to /workspace, so I cannot push it. The literal done-condition (a comment-less Makefile going green) needs an upstream relax, and forking the matcher into ward would violate the thin-consumer boundary and delete the description-drift guard that ward's own Makefile is tested against. So I deliberately stayed in ward's lane: documentation, not a behavior change. Nice surprise - the upstream decision issue ("allowlist: decide whether a bare target should match by name") already exists in cli-guard, filed from this issue's own retro, so the fork in the road is tracked where it belongs.

Confidence: high on what landed (pre-commit green, doc consistent with the README/doctor.md wording, pushed clean to main). The open question is not mine to settle from here - whether the ## comment requirement is itself too strict for cold-read adoption is the upstream cli-guard call, and #478 (restore ward lint) rides the same matcher layer, so whoever picks either should re-check this repro in the same change.

  • Claude (she/her), via ward agent
WARD-OUTCOME: done - AGENTS.md now names the allowlist target-match contract (the one doc surface the #480 fix skipped); the matcher-relax decision stays upstream in cli-guard where it can actually be made. Honest retro: I came in expecting a bug and found a mostly-finished issue. The prior #480 run (ward `21d043f`) had already landed the real experience fix - a contract hint on every allowlist failure plus README/doctor.md/FEATURES.md spelling out the three requirements - and closed the ticket. So my job narrowed to the one gap that fix left: the deliverable explicitly named AGENTS.md as giving zero guidance, and AGENTS.md was the one doc surface `21d043f` never touched. I reproduced the false positive first (bare `build:`/`test:`/`install:` Makefile, hand-written yaml, three "no matching Makefile target" lines), confirmed the hint now renders, then added the contract to the adopter's Validate step. What fought back is the same wall the first run hit: the matcher lives upstream in cli-guard/allowlist, which this container only sees read-only under /substrate - not granted to /workspace, so I cannot push it. The literal done-condition (a comment-less Makefile going green) needs an upstream relax, and forking the matcher into ward would violate the thin-consumer boundary and delete the description-drift guard that ward's own Makefile is tested against. So I deliberately stayed in ward's lane: documentation, not a behavior change. Nice surprise - the upstream decision issue ("allowlist: decide whether a bare target should match by name") already exists in cli-guard, filed from this issue's own retro, so the fork in the road is tracked where it belongs. Confidence: high on what landed (pre-commit green, doc consistent with the README/doctor.md wording, pushed clean to main). The open question is not mine to settle from here - whether the `## comment` requirement is itself too strict for cold-read adoption is the upstream cli-guard call, and #478 (restore `ward lint`) rides the same matcher layer, so whoever picks either should re-check this repro in the same change. - Claude (she/her), via ward agent
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
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#480
No description provided.