qa domains: briefs point subagents at unexpandable ~/.claude paths — 12 failed reads, silent-coverage-loss shape #35
Labels
No labels
component:adapters
component:lifecycle
component:qa
component:setup
component:shared
component:worktrees
phase/accepted
phase/backlog
phase/deployed
phase/developing
phase/integrating
phase/planning
phase/qa
phase/requirements
phase/uat
priority:critical
priority:critical
priority:high
priority:high
priority:low
priority:low
priority:medium
priority:medium
type:bug
type:chore
type:docs
type:feature
type:infra
type:tech-debt
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
jbr870/devwork-skills#35
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Observation
QA domain briefs instruct subagents to Read
~/.claude/skills/_shared/procedures/playwright-conventions.md— verified at HEAD:domains/e2e.md:110,240,289anddomains/a11y.md:376,411. The Read tool does not expand~, and the path also hard-assumes a user-level install. In the slot3/#191 run this cost 12 failed reads across 3 QA agents, each guessing a different wrong home (/home/jochem/, worktree-local.claude/) before finding the devcontainer's$HOME=/home/node. Sharp edge per the report: an agent that gave up would run its domain with no conventions checklist and produce an identical-looking report — silent coverage loss.Recurrence
Mechanical — verified at HEAD (5 occurrences across 2 domain docs). One observed run, but the defect is in the shipped text, not the run.
Hypothesis
The lead resolves the conventions doc's absolute path once (it knows where it loaded skills from) and passes it in the brief — or inlines the few rules the brief actually needs. Any
~/…literal in a brief handed to a subagent is the same bug; worth a lint check for~/.claudeinside domain/procedure text.Suggested lane
meta-amend (text + lint check).
Filed by /meta-retrospective 2026-08-13 (slot3 deep-dive).
/meta-amend outcome — original finding FIXED at HEAD; the residual shape amended
Commit
07d9ad8onmain.Verified at HEAD (
6174683) — the finding as written no longer holdsAll five cited occurrences are gone.
domains/e2e.md:110,240,289anddomains/a11y.md:376,411now read${CLAUDE_PLUGIN_ROOT}/skills/_shared/procedures/playwright-conventions.md; a tree-wide grep finds zero~/.claudepaths anywhere underplugin/. Fixed by the plugin move (#50), not by this issue.The issue's own suggestion — "worth a lint check for
~/.claudeinside domain/procedure text" — also already exists, and is stronger than proposed:lint-conventions.shclass 1 greps the whole shipped tree (-I, every file type, not just markdown — a path in a shipped.jsonis just as unresolvable) for(~|${HOME})/.claude/skills, with a deliberateallow: user-scope-pathanchor for the one line that must document adapter discovery order. So the regression guard is in place.What survives, one substitution later
The
~is gone; the shape is not. A${CLAUDE_PLUGIN_ROOT}/…literal copied into a subagent brief is a path only its writer can resolve — the writer located the file the literal came from, and a freshly spawned agent has located nothing. That is the same silent-coverage-loss failure the issue describes, and nothing in the suite said to resolve it at the writing end.I did not verify whether a spawned agent inherits the substitution — testing it needs a real spawn, which this lane doesn't do — so the rule is written to be correct either way: resolve the path before it goes in the brief; resolving costs nothing if the spawned agent could have resolved it anyway.
The amendment
phase-runner.md§6 — canonical home, chosen because that section already enumerates what is and isn't true inside a spawned agent. New sub-section Resolve plugin paths before they go into a brief: substitute the absolute path, or inline the few rules the brief needs; applies to every path crossing a spawn boundary and to every brief-writing site.qa-playbook.md§2 (the observed site — QA domain briefs) anddevelop/SKILL.md3.2 (both teammate prompts) reference it in one clause each rather than restating it.No new lint check — and why
The issue suggested "text + lint check". The text half is done; the lint half has no target. Briefs are assembled at runtime from domain files and run state — there is no brief in the source tree for a static gate to match, and flagging every
${CLAUDE_PLUGIN_ROOT}in a procedure doc would flag the correct usage as well as the incorrect one, since the same literal is right in a doc and wrong in a brief. The existing class-1 check still covers the~/.clauderegression, which is the part a static gate can see.Gate
scripts/lint-conventions.sh— clean.Desk-check against the originating scenario (slot3/#191)
Three QA agents were handed a conventions-doc path they could not resolve, burned 12 reads guessing (
/home/jochem/, worktree-local.claude/) before finding$HOME=/home/node. Under the amended text the lead resolves the path once — it already knows where it loaded the domain file from — and each brief carries an absolute path. Zero failed reads, and the sharp edge (an agent that gives up and reports as if complete) has no occasion to appear. The devcontainer detail that made this expensive — the lead's$HOMEdiffering from the agents' — is exactly what resolving at the writing end removes, since the resolved path is a fact rather than a per-process variable.What validates it next
A QA run in a container whose
$HOMEdiffers from the driver's — the configuration that produced the original 12 failed reads. Absent that, the change is unfalsifiable by observation: its success looks like nothing happening. The honest bar is that no future run reports a failed read of a suite doc from inside a spawned agent.