From 52f6678c8d2d38dc3f1c092db855bac7d42ec3a7 Mon Sep 17 00:00:00 2001 From: Bastien Chanot Date: Sun, 5 Jul 2026 20:19:13 +0200 Subject: [PATCH 1/2] =?UTF-8?q?feat(skills):=20/deploy=20checklist=20displ?= =?UTF-8?q?ay-only=20=E2=80=94=20no=20NEXT.sh=20file,=20hand-back=20ends?= =?UTF-8?q?=20the=20turn?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Live failure (run 2): the checklist printed above AskUserQuestion never reached the user. Fix is structural: the checklist is never written to a file (throwaway — PENDING.json + live runbook regenerate it in any session) and every hand-back/re-display ends the turn with the full checklist as the FINAL text, no tool call after it. Cold resume without a report regenerates + re-displays. Artifacts 5 -> 4 files; bootstrap gitignore step drops NEXT.sh; mistakes/red-flags updated (no tool call after the print, no file 'for reference'). --- .claude/tasks/TODO.md | 12 +++- CHANGELOG.md | 2 +- skills/deploy/SKILL.md | 120 +++++++++++++++++++--------------- templates/deploy/PROCEDURE.md | 2 +- 4 files changed, 81 insertions(+), 55 deletions(-) diff --git a/.claude/tasks/TODO.md b/.claude/tasks/TODO.md index 971cffa..eea86df 100644 --- a/.claude/tasks/TODO.md +++ b/.claude/tasks/TODO.md @@ -11,8 +11,16 @@ gouverne le bloc entier. - [x] bchanot-cv runbook restylé, committé, pushé (bd7f6e4, develop sync) - [x] settings.json +inputNeededNotifEnabled (layout committé inchangé) - [x] Capitalize EVAL-016 + journal -- [ ] Re-dogfood au prochain /deploy réel (edit de skill non re-testé par run — - dette Iron Law assumée, même statut que la note d'authoring du skill) +- [x] Re-dogfood run 2 (résidus bchanot-cv) : le print inline AVANT + AskUserQuestion ne s'affichait PAS → leçon [[LRN-102]] (texte avant un + tool call peut ne jamais rendre ; le dernier texte du tour est le seul + affichage garanti) +- [x] PASS 2 (feature/deploy-inline-checklist) : checklist DISPLAY-ONLY — + plus de fichier NEXT.sh du tout (jetable, PENDING+runbook régénèrent + partout) ; hand-back TERMINE le tour par la checklist, aucun tool call + après ; resume à froid = régénère + ré-affiche. Skill+template+CHANGELOG. +- [ ] Re-dogfood pass 2 : la fin du deploy run 2 en cours (résidus b24c58b) + exerce le nouveau hand-back ; resume à froid + STEP 4 toujours vierges ## 2026-07-05 — impeccable install chain (feature/impeccable-install) Décision (user a délégué) : COMPLÉMENTAIRES → les deux. frontend-design garde diff --git a/CHANGELOG.md b/CHANGELOG.md index da1c88f..58fa548 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ Format follows [Keep a Changelog](https://keepachangelog.com/). ### Changed - graphify skill dist refreshed 0.8.45 → 0.9.6 (out-of-band `make plugin`; SKILL.md + query/extraction references updated by the generator). -- `/deploy` NEXT.sh reshaped on first-real-run feedback: runbook steps are **one command per line, interactive-session style** (an early step opens the ssh session; later lines run on the box; local steps say "from your machine") instead of folded `ssh host "cd … && …"` one-liners, and the **hand-back prints the full checklist inline** in the conversation (also on every re-hand-back) so the user never has to open `NEXT.sh` to know what to run. Step = comment header + command lines up to the next blank line; a `@delta:` directive governs the whole block. Template `templates/deploy/PROCEDURE.md` restyled to match. +- `/deploy` checklist reshaped on first-real-run feedback, in two passes: runbook steps are **one command per line, interactive-session style** (an early step opens the ssh session; later lines run on the box; local steps say "from your machine") instead of folded `ssh host "cd … && …"` one-liners — step = comment header + command lines up to the next blank line, a `@delta:` directive governs the whole block; and the checklist is now **display-only** — `NEXT.sh` is no longer written at all (throwaway artifact; `PENDING.json` + the live runbook regenerate it in any session) and every hand-back **ends the turn with the full checklist as the final text, no tool call after it** (a checklist printed above a blocking question tool was observed never reaching the user). Template `templates/deploy/PROCEDURE.md` restyled to match. - `settings.json`: `inputNeededNotifEnabled: true` adopted (harness notification toggle); committed layout otherwise unchanged. ### Added diff --git a/skills/deploy/SKILL.md b/skills/deploy/SKILL.md index fa67432..746af12 100644 --- a/skills/deploy/SKILL.md +++ b/skills/deploy/SKILL.md @@ -22,8 +22,9 @@ disk in `.claude/deploy/`, never in conversation context. Never reconstruct the deploy from memory, commit messages, or `git describe`. **Claude never runs the deploy.** Prod commands run by hand, out-of-band. This -skill only writes the checklist (`NEXT.sh`), reacts to the user's report, and -records the outcome. +skill only composes the checklist — **displayed in the conversation, never +written to a file** (it is throwaway: valid for one delta, worthless after) — +reacts to the user's report, and records the outcome. ## The two-moment contract — cold cross-session resume @@ -31,7 +32,7 @@ This is the skill's defining form. No other skill resumes with the context gone. | | | |---|---| -| **Moment 1 (BEFORE)** | STEP 0–2: detect the delta, instantiate `NEXT.sh`, write the `PENDING.json` bridge, hand back. | +| **Moment 1 (BEFORE)** | STEP 0–2: detect the delta, instantiate the checklist, write the `PENDING.json` bridge, hand back. | | **the gap** | The user deploys by hand. May take minutes or days. **May cross sessions.** | | **Moment 2 (AFTER)** | STEP 3–5: on the user's report, react — mark success, or learn from a failure and re-hand-back. | @@ -61,7 +62,7 @@ jq dependency. | Configure deployment settings | `/setup-deploy` | | Document a release after shipping | `/document-release`, `/doc` | -## Artifacts — `.claude/deploy/` (five files) +## Artifacts — `.claude/deploy/` (four files) | File | Committed? | Role | |------|-----------|------| @@ -69,7 +70,11 @@ jq dependency. | `INCIDENTS.md` | yes | `DEP-NNN` ledger, append-only; read at instantiation for pre-warns | | `STATE.json` | yes | deploy oracle — the SHA deployed up to here | | `PENDING.json` | **no (gitignored)** | in-flight bridge; written at hand-back, deleted on success | -| `NEXT.sh` | **no (gitignored)** | instantiated checklist; run BY HAND, never `bash NEXT.sh` | + +The instantiated checklist is **NOT a file**: it is displayed in the +conversation (run BY HAND, step by step, never executed by Claude) and +regenerated on demand from `PENDING.json` + the live runbook. Throwaway by +design — once deployed, it has no value. **Schemas (document of record — recover the shapes from here):** @@ -85,13 +90,13 @@ jq dependency. "runbook_rev": "" } ``` -`step_reached` = where the next `NEXT.sh` must start: `"awaiting-user"` = run from -the top. A numeric `X` is used **transiently within a learn** to regenerate from -step X; **persisted on disk it is always `"awaiting-user"`** — STEP 4 resets to -`awaiting-user` at re-hand-back, and the `runbook_rev` staleness guard is the real -cold-resume regenerate trigger. -`runbook_rev` = the commit sha of `PROCEDURE.md` at instantiation; a mismatch -versus the live runbook means `NEXT.sh` is stale and must be regenerated. +`step_reached` = where the next checklist must start: `"awaiting-user"` = run +from the top. A numeric `X` is used **transiently within a learn** to regenerate +from step X; **persisted on disk it is always `"awaiting-user"`** — STEP 4 +resets to `awaiting-user` at re-hand-back. +`runbook_rev` = the commit sha of `PROCEDURE.md` at instantiation; on resume, a +mismatch versus the live runbook means the runbook changed mid-flight — flag it +and regenerate the checklist against the LIVE runbook. ## `@delta:` grammar (PROCEDURE.md) @@ -129,11 +134,13 @@ Read `.claude/deploy/PENDING.json` **first** (it is the only memory between runs "A deploy started `` is awaiting your report (target ``)." **Do not** recompute the delta, re-read HEAD, or re-instantiate from scratch — the bridge is authoritative. - - *Staleness guard:* if `NEXT.sh` is absent **OR** `runbook_rev` ≠ the live + - *Cold resume without a report yet* (the user just re-invoked /deploy): + regenerate the checklist from the bridge + the live runbook (STEP 2's + expansion, from `step_reached`) and RE-DISPLAY it — the checklist is not + a file, the conversation that held it is gone. If `runbook_rev` ≠ the live runbook commit (`git log -1 --format=%H -- .claude/deploy/PROCEDURE.md`), - the on-disk `NEXT.sh` is stale or missing (a patch landed, or a cold - resume without regeneration) — regenerate it from `step_reached` - (STEP 2's expansion) before reacting. + say so: the runbook changed mid-flight and the regenerated checklist + follows the LIVE version. - **`PENDING.json` absent + `PROCEDURE.md` absent → BOOTSTRAP.** No runbook yet: interview the project and scaffold an annotated `PROCEDURE.md` (or adopt one the user pastes), then continue at STEP 1. *(See STEP 0-B below.)* @@ -165,7 +172,7 @@ Author a runbook, seed the incident ledger, commit both, then proceed to STEP 1. 2. Prepend the standard header: ``` #!/usr/bin/env bash - # === deploy runbook (reference) — NOT run directly. Instantiated to NEXT.sh per delta. === + # === deploy runbook (reference) — NOT run directly. Instantiated into the deploy checklist per delta. === # Fixed steps run every deploy; annotated steps (@delta lines) re-instantiate from the delta. # @config push_deploy_tags=false ``` @@ -226,9 +233,9 @@ Present the full draft `PROCEDURE.md`. 1. Write `.claude/deploy/PROCEDURE.md` (Write tool — the approved draft). 2. Seed `.claude/deploy/INCIDENTS.md` from `templates/deploy/INCIDENTS.md` (Write tool). -3. Ensure the target project's `.gitignore` contains `.claude/deploy/NEXT.sh` and - `.claude/deploy/PENDING.json` (append both if missing — these are the transient - artifacts that must not be committed). +3. Ensure the target project's `.gitignore` contains + `.claude/deploy/PENDING.json` (append if missing — the transient bridge must + not be committed). 4. Check that `.claude/deploy/` is NOT git-ignored: `git check-ignore -q .claude/deploy/PROCEDURE.md` (rc 0 = ignored). If ignored — e.g. the project has `.claude/` in its `.gitignore` wholesale — **ABORT bootstrap**: warn the user that the runbook/oracle/ledger cannot be committed, @@ -270,7 +277,7 @@ Set the base, compute the changed-file list, capture the target. ## STEP 2 — INSTANTIATE + [GATE] + HAND BACK -**Build `NEXT.sh` (the recipe — it IS this shape):** +**Build the checklist (the recipe — it IS this shape):** 1. Walk `PROCEDURE.md` in order. For each step: - un-annotated (fixed) → emit verbatim; @@ -281,15 +288,17 @@ Set the base, compute the changed-file list, capture the target. - `@delta:…when=` → emit verbatim only if the delta intersects a pattern. 2. Read `INCIDENTS.md`; for each `DEP-NNN` whose step matches an emitted step, prepend `# PRE-WARN: DEP-NNN ` above it. -3. Keep every `# VERIFY:` gate. Header the file: *"Run by hand, step by step. - Never `bash NEXT.sh` unattended."* +3. Keep every `# VERIFY:` gate. Header the checklist: *"Run by hand, step by + step. Never executed by Claude."* + base → target SHAs + the delta. 4. Preserve the runbook's shape: one command per line, session style (see the `@delta:` grammar section) — instantiation never re-folds lines. -5. Write `.claude/deploy/NEXT.sh`. +5. **Write NO file.** The checklist exists in the conversation only — + `PENDING.json` is the sole on-disk artifact of the wait, and any future + session regenerates the checklist from it + the live runbook. -**[GATE] — present `NEXT.sh` → `all / edit / skip-all`.** +**[GATE] — present the checklist → `all / edit / skip-all`.** - `all` → proceed. `edit` → revise the listed steps, re-present. -- `skip-all` → abort: write no `PENDING.json`, discard the draft `NEXT.sh`, stop. +- `skip-all` → abort: write no `PENDING.json`, discard the draft, stop. **On approve:** write `.claude/deploy/PENDING.json`: ```jsonc @@ -298,15 +307,17 @@ Set the base, compute the changed-file list, capture the target. "started_at": "", "runbook_rev": "" } ``` -**Then HAND BACK — the checklist lands in the conversation, not just on disk.** -Print the FULL final `NEXT.sh` content inline (fenced code block) so the user -sees exactly what to run without opening the file — the gate preview is not -enough (an `edit` round may have changed it; the hand-back shows the final -text). Then (AskUserQuestion): *"Run NEXT.sh step by step against prod. -Report back: **Deployed OK** / **Failed at step X: ** / **Not yet**."* Then -**stop** — control is the user's; `PENDING.json` on disk now marks the wait. -The same rule applies to every re-hand-back (STEP 4.3): regenerated `NEXT.sh` -⇒ reprinted in full. +**Then HAND BACK — the checklist IS the last text of the turn.** End the turn +with the FULL final checklist in a fenced code block, followed only by the +one-line report request: *"Run it step by step against prod, then report: +**Deployed OK** / **Failed at step X: ** / **Not yet**."* **No tool call +comes after the print — none.** Do NOT wrap the report request in a blocking +question tool: text printed before a tool call may never reach the user +(observed live — a checklist printed above an AskUserQuestion was invisible; +the user had to open the file this rule exists to make unnecessary). The report +arrives as the user's next message; `PENDING.json` on disk marks the wait. +The same rule applies to every re-hand-back (STEP 4.3) and every cold-resume +re-display: regenerated checklist ⇒ full print as the turn's final text. ## STEP 3 — RESUME / REACT @@ -357,13 +368,12 @@ fix (patch + incident committed atomically). Recover later via Then: 1. Bump `PENDING.json.runbook_rev` to `git rev-parse HEAD` (full sha — not the helper's short-hash stdout); keep `step_reached` = `X`. -2. **Regenerate `NEXT.sh` from `step_reached` against the PATCHED runbook** - (steps X…end — X+1…end never ran). This is NOT replaying one step: the bumped - `runbook_rev` is exactly the staleness trigger — runbook changed ⇒ prior - `NEXT.sh` is stale ⇒ regenerate. -3. Re-present via **STEP 2's [GATE] + hand-back** (the regenerated `NEXT.sh`; - `PENDING.json` keeps `base/target/delta`, `step_reached` back to - `awaiting-user`). +2. **Regenerate the checklist from `step_reached` against the PATCHED runbook** + (steps X…end — X+1…end never ran). This is NOT replaying one step: the + runbook changed ⇒ the prior checklist is stale ⇒ regenerate. +3. Re-present via **STEP 2's [GATE] + hand-back** (the regenerated checklist, + full print as the turn's final text; `PENDING.json` keeps + `base/target/delta`, `step_reached` back to `awaiting-user`). ## STEP 5 — MARK (success) @@ -388,8 +398,9 @@ The deploy succeeded. Lay the oracle and close out. bash lib/deploy-commit.sh commit "chore(deploy): mark @ " \ .claude/deploy/STATE.json ``` -6. **Delete `.claude/deploy/PENDING.json` and `.claude/deploy/NEXT.sh`** — the - deploy is no longer in flight; the bridge is consumed. +6. **Delete `.claude/deploy/PENDING.json`** — the deploy is no longer in + flight; the bridge is consumed. (Also remove any legacy `NEXT.sh` left by + an older skill version.) 7. Report: deployed SHA, tag (+ push result), state committed, any `DEP-NNN` learned this deploy. Then offer to capitalize per CLAUDE.md (recurring failure pattern → `learnings.md`; deploy verdict → `evals.md`), gated, never silent. @@ -404,10 +415,13 @@ The deploy succeeded. Lay the oracle and close out. - Delta is `git diff --name-only HEAD` (two endpoints). No `rev-list`, no three-dot, no date ranges. - First-deploy / fresh detection is file existence only — never `git describe`. -- Claude never executes the deploy. `NEXT.sh` is hand-run; `# VERIFY:` gates stay. +- Claude never executes the deploy. The checklist is hand-run; `# VERIFY:` + gates stay. +- The checklist is displayed, never written to a file; every hand-back and + re-display ends the turn with it — no tool call after the print. - Patch + incident commit **atomically**, one `deploy-commit.sh` call, both files. -- A learn bumps `runbook_rev` and **regenerates** `NEXT.sh` from `step_reached`; - it never replays a single step. +- A learn bumps `runbook_rev` and **regenerates** the checklist from + `step_reached`; it never replays a single step. - Tag push is best-effort; `STATE.json` is the oracle. - JSON is read natively (Read tool), never parsed with `jq`/shell. - `STATE.json` written only on confirmed success (STEP 5). A failed/partial deploy @@ -420,9 +434,11 @@ The deploy succeeded. Lay the oracle and close out. | On resume, recomputing delta from current HEAD | HEAD moved during the gap. Use `PENDING.json.{base,target,delta}` verbatim. | | `git describe` to detect first deploy | Errors with no tag. Detect by `STATE.json` / `PENDING.json` existence. | | `git rev-list` or three-dot for the delta | Phantom/undercounted deltas. Two-dot ` HEAD` only. | -| `bash NEXT.sh` to "just run it" | Claude never deploys. Hand back; user runs by hand with `# VERIFY:` gates. | +| Executing the checklist yourself to "just run it" | Claude never deploys. Hand back; user runs by hand with `# VERIFY:` gates. | | Committing the patch without the incident (or vice versa) | Coupling invariant. One atomic `deploy-commit.sh` call, both files. | -| Replaying only the failed step after a patch | Steps X…end never ran. Regenerate `NEXT.sh` from `step_reached`. | +| Replaying only the failed step after a patch | Steps X…end never ran. Regenerate the checklist from `step_reached`. | +| Ending a hand-back with a blocking question tool after the checklist | Text before a tool call may never render. The checklist is the turn's FINAL text; the report comes as the user's next message. | +| Writing the checklist to a file "for reference" | Throwaway artifact — display only; PENDING.json + the runbook regenerate it anywhere. | | Writing `STATE.json` before the user confirms success | Oracle marks success only. Failed deploy leaves it untouched. | | Setting `deployed_sha` to HEAD at MARK time | Use `PENDING.target_sha` — the SHA actually deployed. | | Parsing the JSON bridges with `jq` | Read them natively. No jq dependency. | @@ -432,7 +448,9 @@ The deploy succeeded. Lay the oracle and close out. - About to recompute the delta or re-read HEAD while a `PENDING.json` exists. - About to run `git describe`, `git rev-list`, or a three-dot diff for the delta. -- About to `bash NEXT.sh` or run any prod command yourself. +- About to execute the checklist or run any prod command yourself. +- About to call ANY tool after printing the checklist in a hand-back. +- About to write the checklist to a file. - About to commit `PROCEDURE.md` without `INCIDENTS.md` in the same call. - About to write `STATE.json` before the user reported "Deployed OK". - About to replay one failed step instead of regenerating from `step_reached`. @@ -446,7 +464,7 @@ from it without conversation memory — the `audit-delta` "state file is the onl memory between runs" convention, extended to a *mid-flow* pause. The forms here match the failure modes the design identified: **discipline** failures (recompute-on-resume, run-the-deploy, advance-the-oracle-early) get the -rationalization table + red flags; the **shape** of `NEXT.sh` and the schemas get +rationalization table + red flags; the **shape** of the checklist and the schemas get positive recipes; the patch↔incident **omission** is a structural atomic-commit requirement. Pressure-scenario baseline testing per the writing-skills Iron Law is a follow-up — the failure modes were taken from the design spec, not a fresh diff --git a/templates/deploy/PROCEDURE.md b/templates/deploy/PROCEDURE.md index 61cc09d..992f805 100644 --- a/templates/deploy/PROCEDURE.md +++ b/templates/deploy/PROCEDURE.md @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# === deploy runbook (reference) — NOT run directly. Instantiated to NEXT.sh per delta. === +# === deploy runbook (reference) — NOT run directly. Instantiated into the deploy checklist per delta. === # Fixed steps run every deploy; # @delta: steps re-instantiate from the delta. # @config push_deploy_tags=false # NOTE grammar: glob=:each repeats the command per matching file (e.g. psql -f ); From adf64dfd9d350d26f6c3ca308f8815a002fa8a4a Mon Sep 17 00:00:00 2001 From: Bastien Chanot Date: Sun, 5 Jul 2026 20:19:13 +0200 Subject: [PATCH 2/2] =?UTF-8?q?chore(memory):=20LRN-102=20=E2=80=94=20deli?= =?UTF-8?q?verable=20text=20before=20a=20tool=20call=20may=20never=20rende?= =?UTF-8?q?r=20+=20journal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claude/memory/journal.md | 1 + .claude/memory/learnings.md | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/.claude/memory/journal.md b/.claude/memory/journal.md index 7f13e56..c21a95d 100644 --- a/.claude/memory/journal.md +++ b/.claude/memory/journal.md @@ -330,3 +330,4 @@ rules: - Built /tour skill (grouped sweep clean+security+reconcile+doc, auto, 1..N projects, convergence loop bounded 3×) via writing-skills TDD + skill-creator guidance: RED 6 gaps → GREEN 6/6 closed disk-verified → REFACTOR 2 holes (scratch self-block, BREAKING tag). [[BDR-052]] [[LRN-099]] [[LRN-100]] [[EVAL-014]]. Merged feature/tour-skill → develop + release/1.0.0 on user GO. settings.json /model side-effect reverted (Opus 4.8 1M default restored, attribution backstop kept). - /deploy first real run (bchanot-cv): bootstrap→mark full cycle, live-proven (full security-header stack live — tour→prod closed, tag deploy/2026-07-05). Skill patched post-run on user UX feedback: session-style NEXT.sh (one command per line) + hand-back prints the checklist inline ([[EVAL-016]]); template + generated runbook restyled. impeccable chain + Node 24 baseline shipped develop+RC, pushed. settings.json: +inputNeededNotifEnabled committed (layout unchanged). +- /deploy pass 2 (user feedback live): checklist DISPLAY-ONLY — NEXT.sh file eliminated (throwaway artifact, PENDING+runbook regenerate anywhere), hand-back ends the turn with the checklist as final text (a print above AskUserQuestion never reached the user, [[LRN-102]]). Skill+template+CHANGELOG patched; legacy NEXT.sh removed from bchanot-cv; deploy run 2 (residuals b24c58b) re-handed-back inline. diff --git a/.claude/memory/learnings.md b/.claude/memory/learnings.md index 5380088..1416a73 100644 --- a/.claude/memory/learnings.md +++ b/.claude/memory/learnings.md @@ -119,6 +119,7 @@ rules: | LRN-097 | 2026-07-04 | community blog pattern ≠ official feature — "contexts dir" doesn't exist in Claude Code; verify feature against official docs (claude-code-guide) BEFORE building infra; the intent was already covered by real mechanisms (agents/skills/rules) | any "add support for X" request naming a Claude Code feature | | LRN-099 | 2026-07-05 | auto-orchestrator autonomy boundary: git discipline transfers naturally (branch, no-merge), declared-state discipline does NOT — baseline silently rewrote target TODO + authored registries + scope-crept | designing any auto/headless flow — enumerate declared surfaces, mark each read-only or gated | | LRN-100 | 2026-07-05 | tool gated on clean tree must clean its OWN scratch (else self-DoS next run); contract-changing auto-fix needs structural BREAKING flag in the reviewed artifact | any recurring tool w/ cleanliness precondition; any auto-fix touching an API contract | +| LRN-102 | 2026-07-05 | deliverable text placed BEFORE a tool call may never render — only the turn's FINAL text is guaranteed displayed; a checklist printed above AskUserQuestion was invisible to the user | any flow whose deliverable is conversational text (checklist, commands, report): end the turn with it, blocking questions come before, never after | --- @@ -1041,3 +1042,11 @@ rules: - **context**: 2026-07-04 /tour GREEN on fixture; both patched at REFACTOR (SKILL.md STEP 3). Additions template-structural, NOT re-run through 3rd full pass (cost) — re-test first real use. - **future application**: any recurring tool gated on repo cleanliness → audit what IT leaves behind; any auto-applied fix changing a contract → structural BREAKING flag in the human-reviewed artifact. - **cousin**: [[LRN-099]] same chantier; [[LRN-071]] swallowed-failure class (silent residue ≈ masked state). + +## LRN-102 — Deliverable text before a tool call may never render: the turn's FINAL text is the only guaranteed display + +- **pattern**: /deploy hand-back printed the full checklist in the assistant message, then called AskUserQuestion. The user saw ONLY the question UI — the checklist never reached them ("là on a rien, je dois ouvrir le fichier"). The harness renders reliably only the LAST text of a turn; text between/before tool calls can be swallowed by the tool UI. +- **why**: a skill whose deliverable is conversational (commands to copy-paste, a report) fails silently if any tool call follows the print — the user experiences "nothing displayed" while the transcript technically contains it. Structural fix: the deliverable IS the turn's final text; collect answers BEFORE printing, or let the reply arrive as the next user message. +- **context**: 2026-07-05 /deploy run 2 (bchanot-cv). Skill patched same turn: checklist display-only (no NEXT.sh file at all — user: throwaway once deployed) + hand-back ends the turn, no tool call after. +- **future application**: designing any skill/flow output meant to be read+used from the conversation — put it LAST; never sandwich a deliverable between tool calls; prefer plain-text report requests over blocking question tools after a deliverable. +- **cousin**: [[LRN-100]] same skill lineage; CLAUDE.md communication doctrine (final message carries everything).