ADR-9838 — Docs follow code through claude-code-action on PRs and a close-out Stop hook, from one map
Mirrored from the fleet knowledge base: wi/decisions/ADR-9838-docs-follow-code-through-claude-code-action-and-a-close-out-hook.md
Updated
Mirrored verbatim from
utopia-models/knowledgewi/decisions/ADR-9838-docs-follow-code-through-claude-code-action-and-a-close-out-hook.mdby the allowlisted publish pipeline. Edit it there, not here.
artifact_type: adr
adr_id: ADR-9838
adr_status: accepted
status: active
decision_date: 2026-10-23
decision-makers: [cloud]
consulted: [agent]
informed: [win, nova, dust]
supersedes:
superseded_by:
issue: 9838
subprocess: process
references:
- wi/work/specs/plan-405-session-context-wiring/spec.md
- wi/docs-map.yaml
- _infra/scripts/docs-map.py
- .github/workflows/docs-sync.yml
- wi/components/docs-sync.md
- wi/decisions/TEMPLATE.md
- utopia-models/fleet-plugins#114ADR-9838 — Docs follow code through claude-code-action on PRs and a close-out Stop hook, from one map
Context and Problem Statement
PLAN-405 (Tyler, 2026-10-22: "I really would like to replace our custom workflows with tools and
plugins") asks P3 to keep docs in step with code and process through tools that exist, rather than
hand updates and custom linters. Docs drift today: on 2026-10-23 docs-cog.sh --check is red on
main (phone-comms.md states a timer schedule the unit no longer has) and nothing put that in
front of the author of the change. What decides, for a given change, which doc it owes, and who
writes that doc?
Decision Drivers
- A third-party tool over custom machinery (the north star;
wi/context.md). - No stored copies: the KB already says which doc covers which code — every component doc's
owns:. - Cost: flat subscriptions only. Claude Code runs started by the Action count as subscription usage, not API credits. Measured 2026-10-23, org billing usage API: the free plan's 2,000 hosted Linux minutes were used in full in 2026-09 (knowledge 1,908) and in 2026-10 (knowledge 1,907).
- Masters merge their own PRs within a minute, so a PR-only mechanism misses most changes made in sessions; the session needs its own check at plan close.
Considered Options
- A — claude-code-action on PRs + a Stop hook at plan close, both reading one map (chosen)
- B — hosted-runner claude-code-action only, as in the action's "Documentation Sync" example
- C — the close-out hook only (the issue's named fallback if hosted Actions cannot carry it)
- D — do nothing: keep
docs-cogvalue assertions and kb-lint R10 staleness - E — integrate an existing doc-drift linter (no tool reads
owns:-style mappings; each needs its own config of the same facts)
Decision Outcome
Chosen option: A, because it puts the doc question in front of both kinds of change (PR and
session) with one reader (docs-map.py unmet) and one map, and the model is spent only where a
free check already found a left-behind doc.
- The map is
wi/docs-map.yaml: one shape for all six projects (context+maps), with the bulk derived from component docs'owns:(derive.component_owns) instead of copied. A path covered by several docs is met when any of them changes. - PR side:
.github/workflows/docs-sync.yml,anthropics/claude-code-actionpinned by commit SHA to v1.0.260, authCLAUDE_CODE_OAUTH_TOKEN. It runs on the self-hosted novaserver runners — B is ruled out by the measurement above: a hosted job would queue until the month resets. The action's self-hosted defect (anthropics/claude-code-action#521,claudenot found after install) and its writes to$HOMEare handled by giving the action step a freshHOMEunderRUNNER_TEMP. Nocheck_suite/workflow_runtriggers (a 2026-07 budget-storm amplifier). - Session side: fleet-doctrine
stop-docs-sync.sh, a Stop hook that blocks a plan close once and names the doc; it reads "closed" from the PLAN-405 state file (loop: closed, P4) or the step-2 exit marker. - Lint:
lychee --offline --include-fragmentsruns as an advisory step (continue-on-error) in theprobesjob, the job that already runs thedocs-cogvalue assertions, which stay. - ADRs:
wi/decisions/TEMPLATE.mdis MADR 4.0.0 in the fleet's key names — MADRstatusisadr_status,dateisdecision_date;decision-makers/consulted/informedare added. Superseding edits the OLD record's header (adr_status: superseded,superseded_by:), anddecision-record-linter.shreports the one-way chain. The linter was reading YAML comments and prose as targets and resolving ids infindorder; both fixed, so its findings are real.
Consequences
- Good, because a doc left behind by a PR gets a commit or a written reason, and a session gets one prompt at close, both from the same map.
- Good, because no new list is maintained: adding a component doc with
owns:extends the map. - Bad, because PR-side runs draw on one master login's quota (the token is
nova@utopiamodels.ai's; the repo secret it replaced, set 2026-07-14, was revoked). - Bad, because a PR merged while the action runs loses the doc commit (the comment remains).
Confirmation
python3 _infra/scripts/docs-map.py check
bash _infra/tests/adr-supersede-chain.sh
gh run list --repo utopia-models/knowledge --workflow docs-sync.yml --limit 5
bash _infra/scripts/mechanism-liveness.sh stop-docs-sync.shPros and Cons of the Options
B — hosted claude-code-action only
- Good, because it is the documented example, unchanged.
- Bad, because the org's hosted minutes are spent (measured above), and it never sees session work.
C — close-out hook only
- Good, because it costs no Actions time and no model usage.
- Bad, because a PR from a dispatched master never passes a plan close, so PR-side drift is unchecked.
D — do nothing
- Bad, because value assertions catch a stated number going false, not prose falling behind, and R10 staleness is advisory output nobody is prompted by.
More Information
Re-open if: hosted minutes become available (then the runner choice, not the design, changes); the PR-side run rate measurably threatens master quota; or P4's Plan issue becomes the better place to record "no doc impact" than the session state file.
ADR-9837 — `/plan-start` is a plugin skill that loads ONE project; a start hook keeps it across `/clear`, resume and compaction
Mirrored from the fleet knowledge base: wi/decisions/ADR-9837-plan-start-loads-one-project.md
ADR-9839 — The Plan issue is the only record of a planning session; one loop hook reads the card
Mirrored from the fleet knowledge base: wi/decisions/ADR-9839-the-plan-issue-is-the-only-record.md