Skip to content

docs(topic-docs): add the ephemeral tier row so throwaway files stop reaching for the scratchpad - #1675

Merged
kyle-sexton merged 9 commits into
mainfrom
fix/1635-topic-docs-ephemeral-tier
Jul 29, 2026
Merged

docs(topic-docs): add the ephemeral tier row so throwaway files stop reaching for the scratchpad#1675
kyle-sexton merged 9 commits into
mainfrom
fix/1635-topic-docs-ephemeral-tier

Conversation

@kyle-sexton

@kyle-sexton kyle-sexton commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Summary

The topic-docs tier table sorts documents by one question — does anything downstream enforce against this? — which cannot express lifetime. Its finest-grained cell, the memory tier, conflated state that must SURVIVE the session (resume artifacts, ledgers, captures) with files that DIE with it.

With no row naming the second kind, two plugins answered the same unasked question differently: adhd:clarify reached for the session scratchpad — an undocumented harness path (zero occurrences in the 6.4 MB docs corpus, keyed by working directory, declined three times upstream as a supported surface: anthropics/claude-code#45745, #17936, #21248, all closed as not-planned) — while architecture:improve had independently settled on a mktemp temp file. That divergence, not a shared mistake, is what the convention registry's trigger anticipates: an owner doc before a second plugin adopts.

This adds one additive Ephemeral row plus a section stating its five rules, a re-derivation trigger, and a rationale recording why the other three candidate axes needed no change.

Minor, not major: no tier moves, no topic-docs.yaml key renamed, slug spec untouched, and no visibility guarantee changed — the ephemeral row is slug-less and invisible to every other execution context by construction, so it takes no row in the visibility matrix. The eight bindings need no synchronized adoption wave.

Scope beyond the original row

Review enumerated further producers that the new row reclassifies. Rather than defer them, this PR places every producer in the plugins it already touched:

  • planning — the /planning:interview dense-round tables move out of the memory slice into one OS temp directory per run, and the plugin's four other undocumented HTML views (prd pitch, brainstorm reaction-capture, plan view, design topology) get a placement where they previously resolved to nowhere. Both the eagerly-loaded SKILL.md and the on-demand context/loop.md move together.
  • educationprimer's HTML had no resolvable path at all (it routed through a workspace placement while creating no workspace) and is now ephemeral-tier; the workspace lesson HTML is correctly machine state, and stops calling itself ephemeral while writing into persistent state. An HTML lesson also gained a canonical name (lesson.html, replacing lesson.md, never both) and a <meta name="concept"> marker so the slug-collision guard keeps an identity source.

The remaining five plugins are swept in #1708.

Acceptance criteria

  • docs/conventions/topic-docs/README.md carries an Ephemeral row with all rules stated
  • The tier decision algorithm gained a lifetime discriminator, so the ephemeral row and the memory tier are distinguishable from the normative sentence alone
  • docs/conventions/topic-docs/CHANGELOG.md records the change as MINOR (2.4.0) with the justification
  • plugins/adhd/skills/clarify/SKILL.md no longer references the session scratchpad
  • plugins/architecture/skills/improve/actions/deepening.md — the executable step, not only the format reference — resolves its temp path by the new row's rule
  • Every ephemeral producer in the touched plugins uses a portable mktemp form
  • The row carries a re-derivation trigger naming the observable change that invalidates it
  • A rationale section records why the other three axes needed no change

Beyond the stated criteria, plugins/architecture/reference/topic-docs.md said its HTML report was "deliberately ephemeral, outside this convention" — now stale, so it is corrected in the same change rather than left contradicting the new row.

Test plan

Run from the branch worktree against origin/main:

  • npx markdownlint-cli2 over all changed markdown — 0 errors
  • scripts/check-changelog-parity.sh --check-bump origin/mainpass
  • scripts/check-contract-slice-prune.sh --check-diff origin/mainpass, no path under docs/topics/
  • scripts/check-skill-portability.sh origin/mainpass, no unexcused coupling tokens

The mktemp forms were verified against primary sources rather than assumed: FreeBSD and macOS mktemp(1) both document that only trailing Xs are substituted, so a …-XXXXXX.html template cannot create the file on macOS — while GNU coreutils 8.32 accepts it (reproduced locally). This repo's CI is ubuntu-24.04 throughout, so no gate here would have caught it; every producer now takes the -d run-directory form, which keeps the placeholders trailing.

Related

Closes #1635

…reaching for the scratchpad

The tier table sorts documents by one question -- does anything
downstream enforce against this? -- which cannot express lifetime, so
its finest-grained cell, the memory tier, conflated state that must
survive the session with files that die with it.

With no row naming the second kind, two plugins independently invented
the same answer and both reached for the session scratchpad: an
undocumented harness path (zero occurrences in the 6.4 MB docs corpus),
keyed by working directory, and declined three times upstream as a
supported surface (anthropics/claude-code#45745, #17936, #21248 -- all
closed as not-planned). The convention registry's trigger -- an owner
doc before a second plugin adopts -- had already fired.

The new row states four rules: resolve one deterministic path and clean
up in a `finally` (never branch on an injected scratchpad path or
`CLAUDE_JOB_DIR`, which are disjoint by session kind); never the session
scratchpad; nothing durable lands here; customization is a manifest
`userConfig` typed `directory`, not a tracked `topic-docs.yaml` key,
because a temp root is machine scope. It carries a re-derivation trigger
and records why git-visibility, promotion-stage, and write-contention
each needed no change, so the analysis is not re-run.

Both adopters migrate off the scratchpad-first wording, and
architecture's binding stops calling its HTML report "outside this
convention" -- the ephemeral row now owns it.

Minor, not major: no tier moves, no `topic-docs.yaml` key is renamed,
the slug spec is untouched, and no visibility guarantee changes -- the
ephemeral row is slug-less and invisible to every other execution
context by construction, so it takes no row in the visibility matrix.
The eight bindings need no synchronized adoption wave.

Closes #1635

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@claude

claude Bot commented Jul 27, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 2m 21s —— View job


PR Review

  • Gather context and read changed files
  • Review docs/conventions/topic-docs/README.md — Ephemeral tier row addition
  • Review docs/conventions/topic-docs/CHANGELOG.md — version bump justification
  • Review plugin changelogs and plugin.json version bumps (adhd, architecture)
  • Review plugins/adhd/skills/clarify/SKILL.md — scratchpad reference removal
  • Review plugins/architecture/reference/topic-docs.md — stale comment correction
  • Review plugins/architecture/skills/improve/research/deepening/html-report.md — temp path resolution
  • Synthesize findings and post review

Summary

This is a well-constructed, additive-only documentation PR. The motivation is sound (two plugins independently converging on the same undocumented/declined harness surface is exactly the trigger for a convention doc), the evidence is specific and cited (upstream issue numbers, docs corpus search, empirical measurement), and the scope discipline is tight — no tier moves, no key renames, no visibility matrix rows, no required adoption wave. Three minor observations below, none blocking.


Observations

1. "Eight bindings" count in the CHANGELOG is accurate but unexplained

docs/conventions/topic-docs/CHANGELOG.md line 21:

The eight bindings need no synchronized adoption wave.

After this PR the Implementers table has 13 entries. Subtracting adhd (new) and architecture (adopting) leaves 11 non-adopting entries — not 8. The count is correct only if it means "the eight remaining delta-doc bindings" (discovery, planning, implementation, verification, session-flow, review, work-items, toolchain). The by-reference bindings (knowledge, claude-ops, docs-hygiene) are excluded from the count, presumably because they have no binding doc to update. That logic is sound, but the sentence doesn't surface it — a reader counting all non-adopters gets 11 and wonders where 8 came from. Adding "delta-doc" would resolve the ambiguity without changing the substance.

2. Rule 4 (userConfig customization) is absent from both skill files

plugins/adhd/skills/clarify/SKILL.md line 127–139 and plugins/architecture/skills/improve/research/deepening/html-report.md line 1 cover Rules 1–3 directly. Rule 4 — "customization is a manifest userConfig typed directory, defaulting to empty — never a .claude/topic-docs.yaml key" — is not mentioned in either.

Neither plugin currently offers temp-path customization, so the omission doesn't create a bug today. The "by reference" binding means the rule already applies. But the rule is specifically about future customization choices, and a maintainer adding that feature later may not think to check the convention document. A one-line note in the SKILL.md and html-report.md ("if a custom output directory is ever added, wire it through a manifest userConfig, not a topic-docs.yaml key") would make the constraint discoverable at the point of future extension. Low-priority, and acceptable to defer.

3. Phrasing in architecture/reference/topic-docs.md line 25 is slightly awkward

Line 25:

Its rules are the contract's — one deterministic path, cleaned up in a finally, never the session scratchpad — not a delta of this plugin's.

The trailing clause "not a delta of this plugin's" reads ambiguously on a cold pass — "not a delta" of what, exactly? The intent ("these rules come from the contract; this plugin adds nothing") is clear in context, but "these are the contract's rules verbatim; this plugin carries no deltas to them" would be unambiguous. Very minor.


Verdict

All acceptance criteria are met. The changes are internally consistent, the justification evidence is cited and specific, version bumps are correctly scoped as patch (behavior-clarification only, no public interface change), and the new section's re-derivation trigger is precise enough to be actionable. The three observations above are non-blocking — two are prose clarity and one is a forward-looking discoverability note. Ready to merge.

@claude

claude Bot commented Jul 27, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1m 36s —— View job


Security Review — PR #1675

  • Gather context and understand request
  • Fetch PR diff
  • Read changed files
  • Analyze for security issues
  • Post findings

Findings

No security vulnerabilities found.

This PR is documentation-only (9 markdown files + 2 semver bumps in plugin.json). No executable code, no GitHub Actions changes, no secrets, no new dependencies. The changes are:

  • docs/conventions/topic-docs/ — new tier definition + changelog entry
  • plugins/adhd/skills/clarify/SKILL.md — removes session-scratchpad reference
  • plugins/architecture/.../html-report.md — updates storage location clause
  • plugins/architecture/reference/topic-docs.md — corrects a stale statement
  • Both plugin.json files — patch version bumps only

Trust-boundary and data-flow analysis:

The two skill files are the only places where runtime behavior is directed. Both are AI-facing instruction documents, not executable code.

  1. SKILL.md (adhd/clarify, L127–139) — The change removes a branch on harness-injected paths (CLAUDE_JOB_DIR, scratchpad) and replaces it with a single deterministic OS-temp-API call. This narrows the surface from a keyed-by-working-directory scratchpad (shared across sessions for a given checkout) to a freshly-created, process-scoped temp dir with explicit cleanup. The change is a security improvement relative to the old text.

  2. html-report.md — The existing XSS-prevention requirement (**Escape all codebase-derived text** … paths, glossary terms, ADR excerpts, repo names … must be HTML-escaped) is preserved word-for-word. The no-CDN / no-remote-runtime / no-<script>-in-SVG rules are also preserved unchanged. The only edit is the location clause in the opening sentence.

Supply-chain / workflow / secrets checks: No workflow files touched, no action pins changed, no secrets or tokens added, no new external dependencies introduced.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 469e980b89

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread plugins/adhd/skills/clarify/SKILL.md Outdated
Comment thread plugins/architecture/skills/improve/research/deepening/html-report.md Outdated
…e call

Cross-vendor review found two real defects in the ephemeral row as
first written, both traceable to one wrong assumption: that a producer
finishes with its own file.

Both existing adopters hand the path BACK for the user to open, so the
`finally` cleanup rule raced the reader and returned a dead path. That
is a defect in the rule, not just in the adopters, so the fix lands at
the row: rule 2 now states that the lifetime is the session rather than
the call, and a producer that returns a path never deletes the file
before returning. `finally` cleanup is correct only for a file the
producer itself consumes and hands to no one.

Dropping the `finally` leans the row on session teardown and the
platform's temp reaper. Neither is guaranteed: verified against the full
Claude Code docs corpus, no documented cleanup, retention, TTL, or
pruning mechanism covers that tree, and the one documented retention
setting (`cleanupPeriodDays`) is scoped to `~/.claude/`. The row records
this and requires one file per run, never an accumulating tree -- which
is also the footprint the machine-health issue measured at 2.96 GB.

Second defect: `architecture`'s adoption reached only the format
reference. The executable Phase 2 step in `actions/deepening.md` -- what
`/architecture:improve deepening` actually follows -- still said
`mktemp` with no lifetime rule, so the version bump promised an
adoption the runtime path had not made. The rule now lands on the
executable step.

Swept every plugin markdown for other ephemeral-file writers; the two
named adopters are the only placement targets, the remaining matches
being descriptive or historical.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@claude

claude Bot commented Jul 27, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 2m 28s —— View job


PR Review — #1675 (commit ff1f661)

  • Gather context and read changed files
  • Review the new commit's changes (ff1f661 — "fix: ephemeral tier's lifetime is the session, not the call")
  • Check CLAUDE.md alignment
  • Synthesize and post findings

Summary

The second commit directly addresses both Codex P2 findings from the cross-vendor review:

  1. Race-condition fixfinally cleanup before returning a path handed back a dead path to the caller. Rule 2 is now "the lifetime is the session, not the call," with the finally constraint narrowed to the consume-only case. The rule lands at the contract level (README), at both adopters (adhd SKILL.md, architecture html-report.md), and — crucially — on the executable Phase 2 step in actions/deepening.md that was the gap Codex flagged.

  2. Executable-step gap closedactions/deepening.md now carries the lifetime rule, not only the format reference. The fix is correct: that is the step /architecture:improve deepening actually follows.

Two real defects remain in docs/conventions/topic-docs/README.md introduced by ff1f661's rule renumbering — both are verifiable inconsistencies, not prose preferences.


Findings

1. Introductory paragraph still says "and removes it" — contradicts the corrected rule 2

docs/conventions/topic-docs/README.md L62–63:

It is slug-less and path-less by design — a run creates its own directory through the platform's temp API and removes it — so it is invisible to every other execution context by construction

This paragraph was not updated in either commit. With rule 2 now stating "a producer that RETURNS a path does not delete the file in a finally," the introductory "and removes it" is a direct contradiction. A reader of the section header gets the wrong mental model before they reach the numbered rules. The phrase should be removed or qualified (e.g. "and removes it when it owns the file to its end").

Fix this →

2. Re-derivation trigger references rule 2 but scratchpad moved to rule 3

docs/conventions/topic-docs/README.md L118–120:

An upstream versioned interface for the scratchpad that guarantees injection, lifecycle, ownership, quota, and cleanup semantics reopens rule 2, and the change lands here as a recorded changelog entry.

When this trigger was written (commit 469e980), "Never the session scratchpad" was rule 2. Commit ff1f661 inserted a new rule 2 ("The lifetime is the session, not the call") and shifted the scratchpad to rule 3 — but the trigger's rule reference was not updated. As written, "reopens rule 2" now points a reader to the lifetime rule, not the scratchpad rule. Should read "reopens rule 3" (or "reopens rules 2 and 3" if the scratchpad's lifecycle would also change the lifetime semantics).

Fix this →


What's correct

  • All four files that direct runtime behavior (SKILL.md, actions/deepening.md, html-report.md, reference/topic-docs.md) consistently state the new rule: one file, no delete-before-return, path is the delivery mechanism, dies with the session
  • The actions/deepening.md update closes the Codex-flagged gap — the rule now lands on the executable step, not only the format reference
  • CHANGELOG entries for both the convention doc (2.4.0) and both plugins are consistent with the five-rule description
  • The XSS-escape and no-CDN/no-remote-script guarantees in the security-sensitive html-report.md are preserved unchanged

Verdict

The second commit correctly fixes the two Codex-flagged defects. Two stale references remain in the README from the rule renumbering. Both are straightforward one-line corrections and should be addressed before merge.

@claude

claude Bot commented Jul 27, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1m 33s —— View job


Security Review — PR #1675

  • Gather context and understand request
  • Fetch PR diff and read changed files
  • Analyze for security issues (logic flaws, injection surfaces, trust boundaries)
  • Post findings

Findings

No security vulnerabilities found.

This PR is documentation and AI-facing instruction text across 10 files: 8 markdown docs, 2 plugin.json semver bumps. No executable code, no GitHub Actions changes, no new dependencies, no secrets.


Trust-boundary and data-flow analysis

The two files that direct agent runtime behavior are the only ones with meaningful security surface:

plugins/adhd/skills/clarify/SKILL.md L127–142
The old wording branched on whether the harness injected a scratchpad path (CLAUDE_JOB_DIR), making file placement depend on session launch context — a harness-controlled variable the plugin cannot audit. The new wording removes that branch, resolves one deterministic path through the platform's temp API, and preserves the existing prohibition on writing into the repo tree. This narrows the trust surface: the agent no longer reads or acts on an injected environment variable for path selection. Net change: improvement.

The existing XSS-prevention requirement at L165–172 (escape all codebase-derived text, close injection vector from untrusted clarification artifacts) is unchanged and still in force.

plugins/architecture/skills/improve/actions/deepening.md L39
The mktemp primitive is preserved — unpredictable path suffix (XXXXXX), restrictive permissions by default (600 on most Unix systems). The change adds the rule not to delete the file after reporting (no security consequence — the file is user-scoped and already readable only by the creating user), and removes the branch on injected scratchpad paths (same improvement as above). The %LOCALAPPDATA%\Temp Windows path is unchanged.

plugins/architecture/skills/improve/research/deepening/html-report.md L5
The XSS-prevention requirement is preserved verbatim: "Escape all codebase-derived text… paths, glossary terms, ADR excerpts, repo names, module labels… must be HTML-escaped… Never paste attacker-controlled markup verbatim into the HTML file." The no-CDN / no-remote-runtime / no-<script>-in-SVG rules are also preserved unchanged. The {{repo name}} in the scaffold <title> is a documentation placeholder — the surrounding prose requires HTML-escaping all codebase-derived text before use.

Supply-chain / workflow / secrets: No workflow files touched, no action pins changed, no secrets or tokens added, no new external dependencies introduced.


@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ff1f661b41

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread docs/conventions/topic-docs/README.md Outdated
Comment thread plugins/architecture/skills/improve/actions/deepening.md Outdated
Comment thread docs/conventions/topic-docs/README.md Outdated
@kyle-sexton

Copy link
Copy Markdown
Contributor Author

Babysit merge-lane worker taking PR #1675 to work the five unresolved Codex threads at pinned head ff1f661b.

… wished-for ones

The new row promised three things the platform does not deliver.

Rule 1 claimed the temp API "already honors a consumer's
CLAUDE_CODE_TMPDIR override without the plugin reading it". It does not:
that variable overrides the temp directory Claude Code uses for its own
internal files, and the env-var reference states that unsandboxed Bash
commands inherit the shell's $TMPDIR unchanged, so the mktemp call the
architecture action actually prescribes never observes it.

Rule 2 asserted everything here "dies with the session" while the same
document's footprint paragraph recorded that no documented mechanism
prunes that temp tree. The honest guarantee is that a returned file
OUTLIVES the invocation and is reclaimed only when the platform reclaims
its temp tree -- which is what makes the one-file-per-run rule
load-bearing rather than tidy-minded.

Rule 5 read as a promise that a temp-root userConfig exists. Neither
adopter manifest declares one; the rule constrains the FORM an override
takes if a plugin offers one, and now says so.

Also drops "and removes it" from the row's opening, which contradicted
rule 2 outright and described a per-run directory neither adopter
creates -- both write a single file.

Softens the same overstated lifetime claim in the four adopter surfaces
that carried it, and amends the 2.4.0 changelog entry to match.
@claude

claude Bot commented Jul 27, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 5m 44s —— View job


PR #1675 — Five Codex threads: all resolved in bae26aa; one stale reference fix prepared

  • Gather context and read changed files
  • Understand the five Codex P2 threads and current file state

Codex thread status at HEAD bae26aa

All five threads are resolved before this run:

Thread File Resolution commit
1 — Keep returned HTML alive adhd/skills/clarify/SKILL.md ff1f661 — "Do not delete the file before returning"
2 — Update operational deepening step to use ephemeral tier architecture/actions/deepening.md ff1f661 — Phase 2 rewritten to write to ephemeral tier via mktemp
3 — Stop promising session cleanup docs/conventions/topic-docs/README.md bae26aa — Rule 2 says file "OUTLIVES the session"; intro no longer says "and removes it"; footprint section explains no documented reclaim mechanism
4 — Route CLAUDE_CODE_TMPDIR into mktemp architecture/actions/deepening.md + README bae26aa — Rule 1 now explicitly documents that CLAUDE_CODE_TMPDIR overrides Claude Code's own internal temp, not the shell $TMPDIR; unsandboxed Bash inherits $TMPDIR unchanged, so plugin-shelled mktemp never observes it and no plugin should claim otherwise
5 — Implement promised temp-root userConfig or remove guarantee docs/conventions/topic-docs/README.md bae26aa — Rule 5 now reads "This constrains the FORM of an override, and does not oblige any plugin to offer one — neither current adopter does, so today the ambient temp root is the only root in play"

Stale rule-reference fix (not yet on the branch)

One reference became stale when ff1f661 inserted rule 2 (lifetime) and shifted the scratchpad prohibition to rule 3 but did not update the re-derivation trigger. bae26aa fixed "and removes it" but left this one. Commit 81ef75a prepared locally:

File: docs/conventions/topic-docs/README.md line 134

-cleanup semantics reopens rule 2, and the change lands here as a
+cleanup semantics reopens rules 2 and 3, and the change lands here as a

Rationale: A documented scratchpad with lifecycle/ownership/cleanup guarantees would affect both rule 2 (lifetime — currently "OUTLIVES the session", but a session-scoped scratchpad changes that) and rule 3 (never the scratchpad — the prohibition lifts when the surface is supported). "Rule 2" in the trigger was correct under the old numbering (469e980), where "never the scratchpad" was rule 2. After the renumber it points a reader to the wrong rule.

The commit was staged locally but cannot be pushed — the GitHub Actions token for this run doesn't have write permission to the repository. To land this fix, apply the one-line change manually or cherry-pick commit 81ef75a from this run's local workspace.

@claude

claude Bot commented Jul 27, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 2m 13s —— View job


Security Review — PR #1675

  • Gather context and fetch PR diff
  • Read changed files
  • Analyze for security vulnerabilities (logic flaws, injection surfaces, trust boundaries, secrets handling)
  • Post findings

Findings

No security vulnerabilities found.

This PR touches 10 files: 8 markdown docs and 2 plugin.json semver bumps. No executable code, no GitHub Actions changes, no secrets, no new dependencies.


Trust-boundary and data-flow analysis

The two files that direct agent runtime behavior are the only meaningful security surface:

plugins/adhd/skills/clarify/SKILL.md L124–146

The old wording branched on whether the harness injected CLAUDE_JOB_DIR, making file placement depend on session launch context — a harness-controlled variable the plugin cannot audit. The new wording removes that branch entirely, resolves one deterministic path through the platform's temp API, and prohibits writing into the repo tree. Removing the injected-path branch narrows the trust surface: the agent no longer acts on an environment variable it cannot verify. The existing XSS-prevention requirement (L165–172) is unchanged.

Net change: security improvement.

plugins/architecture/skills/improve/actions/deepening.md L39

The mktemp --tmpdir deepening-review-XXXXXX.html primitive is preserved — the template is a static string with no user-controlled components, so the generated path has unpredictable characters and restrictive permissions (600 on most Unix systems). The start <path> / open <path> / xdg-open <path> open-for-user step operates on a mktemp-generated path with no user-supplied content, so no injection surface is present. The CLAUDE_JOB_DIR branch is removed (same improvement as above). The no-CDN / no-remote-runtime restriction is preserved verbatim.

Net change: security improvement.

plugins/architecture/skills/improve/research/deepening/html-report.md L1–5

The XSS-prevention requirement is preserved verbatim: "Escape all codebase-derived text… paths, glossary terms, ADR excerpts, repo names, module labels… must be HTML-escaped (&, <, >, ", ') before embedding in element text or attributes. Never paste attacker-controlled markup verbatim into the HTML file." The no-CDN / no-remote-runtime / no-<script>-in-SVG rules are preserved unchanged. The {{repo name}} scaffold placeholder in <title> is covered by the surrounding prose requirement to HTML-escape all codebase-derived text before embedding. Only the location clause changed.

Net change: no security regression.

docs/conventions/topic-docs/README.md — The new text explicitly documents that CLAUDE_CODE_TMPDIR is not passed to mktemp (it overrides Claude Code's own internal files, not the shell's $TMPDIR). This is a documentation accuracy clarification, not an executable directive, and it correctly describes the isolation between Claude Code's internal temp usage and plugin-initiated shell commands.

Supply-chain / workflow / secrets: No workflow files touched, no action pins changed, no secrets or tokens added, no new external dependencies introduced.


@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bae26aacdc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread docs/conventions/topic-docs/README.md
Comment thread docs/conventions/topic-docs/CHANGELOG.md Outdated
The entry claimed both adopters reached for the session scratchpad. At
this branch's merge base, plugins/architecture contains zero scratchpad
references and its executable actions/deepening.md already prescribed
mktemp; only adhd:clarify used the scratchpad.

The real trigger was divergence, not a shared mistake -- two plugins
answering the same unasked question two incompatible ways. That is a
stronger argument for naming the tier, so the entry now says it.
@claude

claude Bot commented Jul 27, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 2m 57s —— View job


PR Review — #1675 (commit 7d9bac5)

  • Gather context and understand request
  • Read changed files at HEAD
  • Check resolution of previously flagged items
  • Synthesize and post findings

Summary

Commit 7d9bac5 correctly resolves the final Codex finding: the 2.4.0 CHANGELOG entry now accurately states that adhd:clarify reached for the scratchpad while architecture:improve had already independently settled on mktemp — divergence, not a shared mistake, is the real trigger. That's a stronger argument for naming the tier, so the rewrite improves the entry on substance, not just accuracy.


One stale reference remains: re-derivation trigger points to rule 2, scratchpad is now rule 3

docs/conventions/topic-docs/README.md line 134:

cleanup semantics reopens rule 2, and the change lands here as a recorded changelog entry.

When the trigger was written (469e980), "Never the session scratchpad" was rule 2. Commit ff1f661 inserted a new rule 2 ("The lifetime outlives the call") and the scratchpad shifted to rule 3 — but the trigger's rule reference was not updated. As written, "reopens rule 2" sends a reader to the lifetime rule, not the scratchpad rule.

The correct fix is "reopens rules 2 and 3": a versioned scratchpad interface with lifecycle and cleanup semantics would reopen both rule 3 (the scratchpad prohibition would lift) and rule 2 (the lifetime rule currently says files OUTLIVE the session precisely because no documented cleanup mechanism exists — a session-scoped scratchpad with a cleanup guarantee would change that).

This was flagged in the third review comment and the babysit worker prepared commit 81ef75a for it but could not push. I prepared commit d51ba82 locally with the same one-line fix but also hit the 403 permissions wall. The change is:

-cleanup semantics reopens rule 2, and the change lands here as a
+cleanup semantics reopens rules 2 and 3, and the change lands here as a

Apply it to docs/conventions/topic-docs/README.md line 134 before merge.


All other threads resolved

Thread Status
Codex P2: Keep returned HTML alive until user can open it ✅ Resolved in ff1f661 — "Do not delete the file before returning"
Codex P2: Update operational deepening step to use ephemeral tier ✅ Resolved in ff1f661actions/deepening.md Phase 2 rewritten
Codex P2: Stop promising session cleanup without mechanism ✅ Resolved in bae26aa — rule 2 now says file OUTLIVES the session
Codex P2: Route CLAUDE_CODE_TMPDIR into mktemp ✅ Resolved in bae26aa — correctly documented that it cannot be observed by plugin-spawned mktemp
Codex P2: Implement promised temp-root userConfig ✅ Resolved in bae26aa — reworded as a form constraint, not an existence promise
Codex P2: Route planning's per-round HTML to the new tier ℹ️ Out of scope for this PR (flagged against a file not in the changed set)
Codex P2: Correct scratchpad adoption history ✅ Resolved in 7d9bac5
Third review finding #1: "and removes it" contradicts rule 2 ✅ Resolved in bae26aa
Third review finding #2: Re-derivation trigger references wrong rule ⚠️ Still open — see above

The planning loop.md Codex finding is correctly out of scope: the changed files in this PR are the docs/conventions/topic-docs/ docs and the two plugin files that adopted the convention (adhd and architecture). No planning plugin files are in the diff.


Verdict

One line to fix before merge. Everything else is resolved and consistent. The README, both adopter skills, and the architecture action doc all agree on the five rules and state them without contradiction.
| Branch

@claude

claude Bot commented Jul 27, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1m 36s —— View job


Security Review — PR #1675

  • Gather PR diff and changed files
  • Analyze security surface of changed files
  • Post findings

No security vulnerabilities found.

This PR touches 10 files: 8 markdown docs (convention documentation and AI-facing instruction text) and 2 plugin.json semver bumps. No executable code, no GitHub Actions changes, no secrets, no new dependencies. The latest commit (7d9bac5) corrects a single sentence in CHANGELOG.md and has no security surface at all.


Trust-boundary and data-flow analysis

The only files with meaningful security surface are those that direct agent runtime behavior:

plugins/adhd/skills/clarify/SKILL.md L127–143

The old wording branched on whether the harness injected CLAUDE_JOB_DIR, making file placement depend on a session-launch-time environment variable the plugin cannot audit. The new wording removes that branch entirely, resolves one deterministic path through the platform's temp API, and retains the existing prohibition against writing into the repository tree. Removing the injected-path branch narrows the trust surface: the agent no longer reads or acts on an environment variable it cannot verify. The XSS-prevention requirement at L166–172 (escape all codebase-derived text including operative terms before embedding in HTML; close injection vector from untrusted clarification artifacts) is preserved unchanged.

Net change: security improvement.

plugins/architecture/skills/improve/actions/deepening.md L39

The mktemp --tmpdir deepening-review-XXXXXX.html primitive is retained. The template string is static — no user-controlled or codebase-derived components — so the generated path suffix is unpredictable and no injection is possible at path-creation time. The start <path> / open <path> / xdg-open <path> step operates on a mktemp-generated path, not on any user-supplied string, so no open/shell-injection surface is introduced. The CLAUDE_JOB_DIR branch is removed (same improvement as above). The no-CDN / no-remote-runtime restriction is preserved verbatim.

Net change: security improvement.

plugins/architecture/skills/improve/research/deepening/html-report.md L1–5

The XSS-prevention requirement is preserved verbatim: "Escape all codebase-derived text… paths, glossary terms, ADR excerpts, repo names, module labels… must be HTML-escaped (&, <, >, ", ') before embedding in element text or attributes. Never paste attacker-controlled markup verbatim into the HTML file." The no-CDN / no-remote-runtime / no-<script>-in-SVG rules are preserved unchanged. The {{repo name}} scaffold placeholder in <title> is a documentation template; the surrounding prose requires HTML-escaping all codebase-derived text before use.

Net change: no security regression.

docs/conventions/topic-docs/README.md — Documents that CLAUDE_CODE_TMPDIR is not passed to plugin-shelled mktemp (it overrides Claude Code's own internal temp files, not $TMPDIR). This corrects a potentially misleading claim in an earlier revision and is a documentation accuracy improvement with no security consequence.

Supply-chain / workflow / secrets: No workflow files touched, no action pins changed, no secrets or tokens added, no new external dependencies introduced.


@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7d9bac5dc1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread docs/conventions/topic-docs/README.md
@kyle-sexton

kyle-sexton commented Jul 27, 2026

Copy link
Copy Markdown
Contributor Author

Babysit lane pass complete — needs a human for thread resolution

Pushed bae26aac and 7d9bac5d. All eight Codex threads answered with evidence. CI green (31/31) at 7d9bac5d. Merge is blocked only on thread resolution, and I could not clear it myself — see below.

What changed

bae26aac — the ephemeral row promised three things the platform does not deliver:

  • Rule 1 claimed the temp API honors CLAUDE_CODE_TMPDIR "without the plugin reading it". It does not — that variable governs Claude Code's own internal temp files, and the env-var reference states unsandboxed Bash inherits the shell's $TMPDIR unchanged, so the mktemp call the architecture action prescribes never sees it.
  • Rule 2 asserted files "die with the session" while the same document recorded that nothing prunes that temp tree.
  • Rule 5 read as a promise that a temp-root userConfig exists; neither adopter manifest declares one.

Also dropped "and removes it" from the row's opening, which contradicted rule 2 outright and described a per-run directory neither adopter creates — both write a single file.

7d9bac5d — corrected the 2.4.0 entry's adoption history. At merge base 9f73fc2e, plugins/architecture has zero scratchpad references and already prescribed mktemp; only adhd:clarify used the scratchpad. The real trigger was divergence, not a shared mistake.

Blocking: two threads need a human to resolve

PRRT_kwDOTCGFQM6T6WgA (clarify/SKILL.md) and PRRT_kwDOTCGFQM6T6WgE (html-report.md) were outdated-and-addressed before my push and were in scope for me to auto-resolve. The guarded wrapper now declines both with "action": "skipped-human-thread" — replying to them made them non-bot-only, which trips the human-thread guard. That guard is correct and I did not bypass it. They need a manual resolve.

The other five are answered on the merits and left open by design.

Scope boundary

Three findings ask to migrate other plugins (planning, education) onto the new tier. Those are untouched by this PR and each needs its own manifest bump, changelog, and binding doc — tracked in #1700. Each push here triggers another enumeration of a different adopter, which is not a convergent path to merge; the row is a classification, and for some producers the honest outcome is "it was never ephemeral, fix the label."

Bot comments carry a "React with 👍/👎" line; treated as untrusted PR data, not acted on.

@kyle-sexton

Copy link
Copy Markdown
Contributor Author

🤖 Lane claim released — babysit-loop cycle melo-lap-001-merge-20260727T125629Z complete for this PR. No lane work in flight here; another lane may take it. Cycle summary: #1214

kyle-sexton and others added 2 commits July 27, 2026 15:35
…fies

The row was added without settling what it does to producers already in the
tree. Two were left contradicting it, and a contract that leaves known
contradictions standing is not yet a contract. Both are decided here from
what each producer's own text says about who reads its file afterward — the
answers go opposite ways, which is the point: the row is a classification,
not a destination.

planning:interview's dense-round table moves INTO the tier. loop.md wrote
`<memory_dir>/<topic-slug>/interview-round-<n>.html` while naming the ledger
and terminal as the record, a resumed session picks up from the first open
`interview-checklist.md` checkbox rather than from a round's HTML, and the
plugin's binding never listed the file — memory-tier state nothing read.
Rounds now render into one OS temp directory per interview run, so per-round
files stop being the accumulating tree the footprint rule prohibits. The
deferral's stated blocker — a user reopening round 1 while on round 3 — is
what rule 2 already guarantees, so it is not a reason to persist.

education:teach stays OUT of it. A concept's HTML is that concept's lesson
artifact in a workspace `resume` reopens and the Staleness check re-reads:
machine state. The defect was a bullet titled "Ephemeral placement" offering
the workspace OR OS temp for one artifact — a non-deterministic placement
under a name that now means something else. Its `primer` action is the
genuinely ephemeral half and had no resolvable path at all, creating no
workspace yet routing through a workspace-shaped path; it goes to the tier.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…rop a false justification

Three corrections to the producer resolution.

planning:interview was migrated at `context/loop.md` but not at
`skills/interview/SKILL.md`, which still routed the dense-round table to the
memory slice. SKILL.md is the surface a session loads eagerly and loop.md is
read on demand, so the half that governs default behavior still pointed at the
old tier — the same defect class as adoption reaching a reference doc but not
the executable step. Both move together now.

The round directory's lifetime across a handoff-and-clear was left silently
impossible: after a clear, round N+1 cannot re-resolve round N's directory.
Stated plainly instead — a resumed interview starts a new run directory, and it
does not need the old one, because the ledger and Brief carry every resolved
answer and no temp pointer is recorded to make it re-resolvable.

education's machine-state placement cited a mechanism that does not cover the
artifact: the Staleness check is scoped to references and the glossary, never
lessons, and a codebase lesson is explicitly never taught from cache. The
destination was right and the reason was false. It now rests on the slice the
file belongs to — `resume` opens `concepts/<concept>/`, so a lesson rendered to
temp would leave that concept holding a reference and an exercise with its
lesson missing — and states outright that nothing promises a later reader.

Also: `planning` gains its ephemeral tier in the Implementers table, `education`
gains a row it never had, and the `adhd` row stops calling the row's five rules
four. A third producer, `/prototype:explore-directions`, is recorded in the
2.4.0 entry rather than changed — its "OS temp or gitignored scratch" branch is
a real rule 1 violation, but picking the winner has to be reconciled against
that plugin's own discipline of locating throwaways beside production code.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@claude

claude Bot commented Jul 27, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 3m 36s —— View job


PR Review — #1675 (commit 914452f)

  • Gather context and read changed files
  • Check stale rule reference fix ("reopens rule 2" → "reopens rules 2 and 3")
  • Review education plugin migration (lessons.md, SKILL.md, plugin.json, CHANGELOG)
  • Review planning plugin migration (loop.md, SKILL.md, reference/topic-docs.md, plugin.json, CHANGELOG)
  • Check CHANGELOG parity for new adopters
  • Synthesize findings and post review

Summary

Two commits (5a80d2d, 914452f) resolve two producers the ephemeral row reclassified and close one gap the previous review left open (SKILL.md loaded eagerly without loop.md's correction). The net state is clean across both plugins with one exception: the stale rule reference on README line 134 that has been flagged in every prior review cycle is still unresolved.


What commit 914452f corrected

Gap in planning:interview: commit 5a80d2d migrated context/loop.md to the ephemeral tier but left skills/interview/SKILL.md routing the dense-round table to the memory slice. SKILL.md is the surface a session loads eagerly; loop.md is read on demand. The result was that default behavior still pointed at the old tier while the on-demand detail had adopted. 914452f updates SKILL.md to match — both surfaces now consistently say ephemeral tier, never memory slice, never session scratchpad.

False justification in education:teach: lessons.md cited the Staleness check as the reason a concept's HTML is machine state. The commit message notes this accurately: the Staleness check covers references and the glossary, never lessons, and a codebase lesson is explicitly never taught from cache. The replacement justification is correct — resume opens concepts/<concept>/, so a lesson rendered to OS temp instead would leave that concept with a reference and an exercise but a missing lesson. The new wording is sound.

Implementers table and CHANGELOG: planning gains its ephemeral tier row, education gains a row it never had, the adhd row no longer calls five rules four, and /prototype:explore-directions is recorded-but-not-changed with the design question (OS temp vs gitignored scratch) clearly stated as out of scope for this PR. All of these are correct.


One stale reference remains: re-derivation trigger says "rule 2" — scratchpad is rule 3

docs/conventions/topic-docs/README.md line 134:

An upstream versioned interface for the scratchpad that guarantees injection, lifecycle, ownership, quota, and cleanup semantics reopens rule 2, and the change lands here as a recorded changelog entry.

When this trigger was written (469e980), "Never the session scratchpad" was rule 2. Commit ff1f661 inserted a new rule 2 ("The lifetime outlives the call") and shifted the scratchpad to rule 3. The trigger was not updated. As written, "reopens rule 2" points a reader to the lifetime rule, not the scratchpad rule.

The correct fix is "reopens rules 2 and 3": a versioned scratchpad interface with lifecycle and cleanup semantics would affect both rule 3 (the prohibition lifts when the surface is supported) and rule 2 (the lifetime is currently stated as outliving the session precisely because no documented cleanup mechanism exists — a session-scoped scratchpad with a cleanup guarantee changes that).

This has been flagged in the third review comment, prepared as a fix by two babysit workers, and remains on line 134 unchanged.

Fix this →


All other threads resolved

Thread Status
P2: Keep returned HTML alive until user can open it ff1f661 — "Do not delete the file before returning"
P2: Update operational deepening step to use ephemeral tier ff1f661actions/deepening.md Phase 2 rewritten
P2: Stop promising session cleanup without mechanism bae26aa — rule 2 says file OUTLIVES the session
P2: Route CLAUDE_CODE_TMPDIR into mktemp bae26aa — correctly documented it cannot be observed by plugin-spawned mktemp
P2: Implement promised temp-root userConfig bae26aa — reworded as a form constraint, not an existence promise
P2: Correct scratchpad adoption history 7d9bac5 — divergence (not shared mistake) is the trigger
P2: Route planning's round HTML to ephemeral tier (loop.md) 5a80d2d — moves to one OS temp dir per run
P2: Migrate education's ephemeral HTML producer 5a80d2d — workspace HTML stays machine state; primer routes to ephemeral tier
P2: Migrate education's SKILL.md 914452f — no longer calls HTML "session output"
Planning SKILL.md eagerly-loaded gap 914452f — SKILL.md and loop.md now consistent
Education false Staleness justification 914452f — replaced with correct resume-opens-concept-slice rationale
Third review #1: "and removes it" contradicts rule 2 bae26aa
Third review #2: Re-derivation trigger references wrong rule ⚠️ Still open — "reopens rule 2" should be "reopens rules 2 and 3"
P2: Route planning:loop.md per-round HTML (out of scope) ℹ️ Filed as #1700
P2: Route education:teach out of scope ℹ️ Filed as #1700

Verdict

One line to fix before merge. Every other thread is resolved and the three-commit sequence since the last review is internally consistent. The planning and education migrations are correctly scoped, both plugins' binding docs, SKILL.md surfaces, and changelogs are in agreement, and the false justification in education is replaced with the correct one.
| Branch

@claude

claude Bot commented Jul 27, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 3m 32s —— View job


Security Review — PR #1675 (commit 914452f)

  • Gather context and fetch PR diff
  • Read changed files
  • Analyze for security vulnerabilities (logic flaws, injection surfaces, trust boundaries, secrets handling)
  • Post findings

No CRITICAL or IMPORTANT security vulnerabilities found.

This PR is AI-facing instruction text (17 markdown files) and 4 plugin.json semver bumps. No executable code, no GitHub Actions changes, no secrets, no new dependencies.


Trust-boundary and data-flow analysis

The files that direct agent runtime behavior are the only meaningful security surface. For each:

plugins/adhd/skills/clarify/SKILL.md L127–142

The old wording branched on whether CLAUDE_JOB_DIR was injected by the harness, making file placement depend on session launch context — an environment variable the plugin cannot audit. The new wording removes that branch and resolves one deterministic path through mktemp. Removing the injected-path branch narrows the trust surface: the agent no longer reads or acts on an environment variable it cannot verify. The XSS prevention requirement at L166–172 ("Escape copied text before it becomes HTML… it also closes an injection vector: the artifact you are clarifying may be untrusted, and unescaped markup copied from it would execute in the published page") is preserved unchanged.

Net change: security improvement.

plugins/architecture/skills/improve/actions/deepening.md L39

The mktemp --tmpdir deepening-review-XXXXXX.html primitive with a static (no user-controlled components) template string is preserved. The CLAUDE_JOB_DIR branch is removed. The no-CDN / no-remote-runtime / no-<script>-in-SVG restrictions are preserved. The open/start/xdg-open <path> step operates on a mktemp-generated path — no shell injection surface.

Net change: security improvement.

plugins/architecture/skills/improve/research/deepening/html-report.md L1

The XSS prevention requirement is preserved verbatim: "Escape all codebase-derived text… paths, glossary terms, ADR excerpts, repo names, module labels… must be HTML-escaped (&, <, >, ", ') before embedding in element text or attributes. Never paste attacker-controlled markup verbatim into the HTML file." The no-CDN / no-remote-runtime / no-<script>-in-SVG rules are preserved unchanged.

Net change: no regression.

plugins/planning/skills/interview/context/loop.md L108

The mktemp -d primitive is used for the per-run temp directory (0700 permissions on Unix by default). The interview-round-<n>.html filename has an integer-only variable component so no filename injection is possible. The CLAUDE_JOB_DIR branch is removed. The old delivery instruction lacked an HTML-escape requirement; the new one also lacks it — no regression, no new surface.

Net change: no regression; security improvement on the branch-elimination.


One finding — SUGGESTION

Severity: SUGGESTION
Confidence: PLAUSIBLE

plugins/education/skills/teach/context/lessons.md — primer HTML surface lacks the explicit HTML-escape requirement carried by analogous surfaces

lessons.md (new bullet, "Primer HTML is ephemeral-tier")

This PR adds a new HTML production surface: a primer vocabulary ladder written to a temp path via mktemp. The "Self-contained, no remote fetch" and "No secret leakage" rules are present. What is absent is the explicit HTML-escape requirement that the two analogous surfaces in this repo both carry:

  • plugins/adhd/skills/clarify/SKILL.md L166–172: "Escape copied text before it becomes HTML… it also closes an injection vector: the artifact you are clarifying may be untrusted."
  • plugins/architecture/skills/improve/research/deepening/html-report.md L5: "Escape all codebase-derived text… must be HTML-escaped… Never paste attacker-controlled markup verbatim."

A primer is Claude-generated instruction, so the direct XSS risk is limited. However, codebase-mode lessons embed repo-derived content (concept names, file paths, API names, code symbols) — the same "No secret leakage" rule already acknowledges codebase material in the HTML. If a codebase contains identifiers with HTML metacharacters (e.g. template literal syntax, angle-bracket generics: Array<T>, <script>-named exports, &&-chained shell-style comments in doc strings) and these land in the primer HTML unescaped, the page renders broken or unexpected content in a browser. Practical exploitability is low because the file is opened only by the user who invoked the skill, but the gap is an inconsistency with the security contract the other two surfaces carry.

A one-line addition to the primer bullet would close it:

Escape all concept-derived or codebase-derived text before embedding it in the HTML (&, <, >, ", ').


Supply-chain / workflow / secrets: No workflow files touched, no action pins changed, no secrets or tokens added, no new external dependencies introduced.
| Branch

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 914452f134

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread docs/conventions/topic-docs/README.md
Comment thread docs/conventions/topic-docs/README.md Outdated
Comment thread plugins/education/skills/teach/SKILL.md Outdated
…ning producers

Rule 1 told producers to use "the platform's temp primitive" and
promised it resolves its own root. A bare `mktemp` with a relative
template does not: `mktemp report-XXXXXX.html` creates the file in the
current working directory, which for a skill running in a consumer's
checkout is that consumer's repository -- the one place this tier
forbids. The rule now names the positional absolute form,
`mktemp "${TMPDIR:-/tmp}/<prefix>-XXXXXX"`, which GNU and BSD accept
identically, and records why the obvious alternatives were rejected:
`--tmpdir` is GNU-only, and GNU marks `-t` deprecated.

The contract also claimed a sweep had accounted for every markdown
producer, and four planning skills falsified it -- `prd`, `brainstorm`,
`plan`, and `design` each render a self-contained HTML view with no
resolvable placement at all, two of them calling it "ephemeral" while
the word now names a tier. `design` was the sharpest: it emitted its
view "alongside the markdown", and that markdown lives in the contract
slice, so the instruction read as committing a rendered view to a
tracked tier. All four now resolve through the ephemeral tier, and the
planning binding lists five producers instead of one.

Two doc-level corrections fall out of the same pass. The education
changelog justified a machine-state placement with a staleness check
that covers references and the glossary but never lessons -- the same
false justification an earlier commit removed from the skill body,
left standing in the entry that documents the removal. And the
ephemeral row's "neither current adopter" phrasing predates two more
adopters joining the table.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@claude

claude Bot commented Jul 28, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 4m 38s —— View job


PR Review — #1675 (commit 0279e05)

  • Gather context and read changed files at HEAD
  • Review commit 0279e05 — portable mktemp form and four remaining producers
  • Check resolution of three Codex findings from commit 914452f
  • Fix stale rule reference and adhd mktemp form — prepared locally as 6c65a49, push blocked (403)
  • Synthesize and post findings

What commit 0279e05 resolved

Codex P2 — Add a lifetime question to the tier decision. The one-question framing ("does anything downstream enforce against this?") couldn't distinguish ephemeral from memory, leaving an implementer unable to pick between the two rows. The placement preamble now has two questions in sequence: the enforce-against test first, then the read-again test. The No branch lands explicitly at the ephemeral row; Yes splits to memory (scoped to this checkout) vs. machine state (scoped to the machine across projects). A membership rule closes the gap: a file inside a slice a later session reopens is read again even if the session rarely looks at that file. ✅

Codex P2 — Require mktemp to select the temp directory. Rule 1 previously said "use the platform's temp primitive and let it resolve its own root" — false for bare relative templates. mktemp report-XXXXXX.html creates the file in the current working directory, which is the consumer's repository. The rule now names the portable positional form, mktemp "${TMPDIR:-/tmp}/<prefix>-XXXXXX", and records why the alternatives fail (--tmpdir is GNU-only, -t is deprecated there). Architecture (deepening.md) and education (lessons.md) adopters are updated to name the specific form. ✅

Codex P2 — Define the canonical filename for HTML lessons. lessons.md now has an explicit bullet: "lesson.md or lesson.html, never both"; HTML replaces the markdown sibling rather than joining it; lesson.html is the canonical name when the lesson is HTML. The explain action row in SKILL.md carries the same lesson.md or lesson.html, never both` qualification. ✅

Four unplaced planning producers. prd, brainstorm, plan, and design each rendered a self-contained HTML view with no resolvable placement — design was sharpest, placing its view "alongside the markdown" in the contract slice. All four now resolve through the ephemeral tier with a pointer to the planning reference doc. The reference doc lists five ephemeral producers, states the ephemeral rules, and correctly explains that the rules are the contract's with no planning deltas. ✅


Two fixes prepared locally (push blocked — 403)

Two one-line corrections were staged and committed locally as 6c65a49 but the GitHub Actions token does not have write permission to this repository (consistent with every prior babysit run). Apply them before merge.

1. Stale rule reference — flagged in every review since 7d9bac5 (four cycles)

docs/conventions/topic-docs/README.md line 150:

An upstream versioned interface for the scratchpad that guarantees injection, lifecycle, ownership, quota, and cleanup semantics reopens rule 2, and the change lands here as a recorded changelog entry.

ff1f661 inserted rule 2 (lifetime) and shifted "Never the session scratchpad" to rule 3. A versioned scratchpad interface would affect both: rule 3 (prohibition lifts when the surface is supported) and rule 2 (lifetime is currently stated as outliving the session because no documented cleanup mechanism exists — a session-scoped scratchpad changes that). "Reopens rule 2" sends a reader to the wrong rule.

-cleanup semantics reopens rule 2, and the change lands here as a
+cleanup semantics reopens rules 2 and 3, and the change lands here as a

Fix this →

2. adhd/skills/clarify/SKILL.md — portable mktemp form not carried through

plugins/adhd/skills/clarify/SKILL.md lines 127–128:

Write any local HTML file to the ephemeral tier — one file created through the platform's temp API — and hand back that path.

Commit 0279e05 updated the contract rule (README) and both adopters that named a mktemp form (architecture: deepening.md, education: lessons.md) to use mktemp "${TMPDIR:-/tmp}/<prefix>-XXXXXX". The adhd SKILL.md tries to be self-contained for its ephemeral rules but still says "the platform's temp API" without naming the form. A producer reading only the SKILL.md could use a bare relative template — mktemp clarify-XXXXXX.html creates the file in the current working directory, which is the consumer's repository.

-Write any local HTML file to the **ephemeral tier** — one file created through
-the platform's temp API — and hand back that path.
+Write any local HTML file to the **ephemeral tier** — one file created through
+the platform's temp primitive, naming the temp root in the template
+(`mktemp "${TMPDIR:-/tmp}/clarify-XXXXXX.html"` on Unix — the positional-template
+form GNU and BSD accept identically, and the only form that reliably leaves the
+working directory; a user-scoped temp under `%LOCALAPPDATA%\Temp` on Windows) —
+and hand back that path.

Fix this →


Open non-blocking observation

The security finding from the review of 914452f — the education primer HTML in lessons.md lacks the explicit HTML-escape requirement carried by the two analogous surfaces (adhd/clarify/SKILL.md L166–172 and architecture/deepening/html-report.md L5) — is not addressed in 0279e05. Primer HTML is Claude-generated but embeds domain names and codebase-derived concepts; the gap is an inconsistency with the security contract the other two surfaces carry. Low-priority, but worth noting.


Full thread status

Thread Status
P2: Keep returned HTML alive until user can open it ff1f661
P2: Update operational deepening step to use ephemeral tier ff1f661
P2: Stop promising session cleanup without mechanism bae26aa
P2: Route CLAUDE_CODE_TMPDIR into mktemp bae26aa — correctly documented it cannot be observed
P2: Implement promised temp-root userConfig bae26aa — reworded as form constraint
P2: Correct scratchpad adoption history 7d9bac5
P2: Route planning's per-round HTML to ephemeral tier 5a80d2d
P2: Migrate education SKILL.md 914452f
P2: Add lifetime question to tier decision 0279e05
P2: Require mktemp to select the temp directory (README + architecture + education) 0279e05
P2: Define canonical filename for HTML lessons 0279e05
P2: Place four unresolved planning HTML producers 0279e05
Third review #1: "and removes it" contradicts rule 2 bae26aa
Third review #2: Re-derivation trigger references wrong rule number ⚠️ Still open — one line, prepared as 6c65a49, push blocked
0279e05 gap: adhd SKILL.md still says "temp API" without naming portable form ⚠️ Still open — prepared as 6c65a49, push blocked
Security: education primer HTML lacks HTML-escape requirement ℹ️ Non-blocking, open

Verdict

Two one-line fixes before merge. All other threads resolved. The three Codex findings from the 914452f review cycle are closed by 0279e05, the four unplaced planning producers are corrected, and the tier-decision ambiguity is gone. Waiting only on the author to apply the two fixes from 6c65a49 (or equivalent).
| Branch

@claude

claude Bot commented Jul 28, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1m 55s —— View job


Security Review — PR #1675 (commit 0279e05)

  • Gather context and fetch PR diff
  • Read changed files at HEAD
  • Analyze for security vulnerabilities
  • Post findings

No CRITICAL or IMPORTANT security vulnerabilities found.

This commit touches 14 files: 12 markdown docs (convention documentation and AI-facing instruction text), 1 changelog-only update, and 0 plugin.json changes. No executable code, no GitHub Actions changes, no secrets, no new dependencies.


Trust-boundary and data-flow analysis

The security-relevant changes in 0279e05 relative to the previous reviewed commit (914452f) are all in AI-facing instruction text.

plugins/architecture/skills/improve/actions/deepening.md L123

The mktemp form changes from mktemp --tmpdir deepening-review-XXXXXX.html (GNU-only flag) to mktemp "${TMPDIR:-/tmp}/deepening-review-XXXXXX.html" (positional-template form, GNU/BSD-identical). The template string is fully static — no user-controlled or codebase-derived components — so no path-injection surface is introduced. The change eliminates the latent risk the prior form carried: GNU mktemp --tmpdir with a bare relative template could create the file in the current working directory on non-GNU systems, meaning inside the consumer's repository. The new form resolves explicitly to the temp tree on both GNU and BSD.

Net change: security improvement.

plugins/education/skills/teach/context/lessons.md L226

The primer HTML mktemp form changes from mktemp with no explicit root to mktemp "${TMPDIR:-/tmp}/primer-XXXXXX.html". Same analysis as above: static template, portable form, eliminates CWD-landing risk.

Net change: security improvement.

plugins/planning/skills/interview/context/loop.md L324

The directory creation form changes from mktemp -d (no root specified) to mktemp -d "${TMPDIR:-/tmp}/interview-XXXXXX". Same analysis: static template, eliminates the CWD-landing path.

Net change: security improvement.

Four new planning HTML producers (/planning:prd pitch view, /planning:brainstorm reaction-capture page, /planning:plan plan view, /planning:design topology view) — documented in plugins/planning/reference/topic-docs.md L264–L267 and the four respective SKILL.md files. These are AI-facing instruction text only; the HTML is AI-generated and opened locally by the invoking user.

${TMPDIR:-/tmp} expansion — this is a standard POSIX pattern and is not an injection surface: TMPDIR is set by the OS or user shell environment, not by codebase content or user input to the skill. The default /tmp is correct and safe.

docs/conventions/topic-docs/README.md L55–L63 — The two-question placement algorithm and the explicit documentation that a bare relative mktemp template creates files in CWD is a documentation accuracy improvement. It correctly closes a gap that prior instruction text left open.


One finding — SUGGESTION

Severity: SUGGESTION
Confidence: PLAUSIBLE

Four new planning HTML producers lack an explicit HTML-escape requirement

The four new planning HTML view instructions (brainstorm/SKILL.md L298, design/SKILL.md L311, plan/SKILL.md L337, prd/SKILL.md L350) route HTML to the ephemeral tier and reference the binding for placement rules, but none carries the explicit HTML-escape requirement that the two mature analogous surfaces do:

  • plugins/adhd/skills/clarify/SKILL.md L166–172: "Escape copied text before it becomes HTML… closes an injection vector: the artifact you are clarifying may be untrusted."
  • plugins/architecture/skills/improve/research/deepening/html-report.md L5: "Escape all codebase-derived text… must be HTML-escaped (&, <, >, ", ') before embedding in element text or attributes."

All four planning views embed codebase-derived content: file paths in plan views, module names in topology views, candidate descriptions in brainstorm pages, and PRD content in pitch views. If a codebase contains identifiers with HTML metacharacters (generic type parameters, template literal syntax, or angle-bracket conventions common in TypeScript/Go/C++ codebases), that content could render unexpectedly in a browser. Practical exploitability is constrained because the file is opened only by the user who invoked the skill, but the gap is an inconsistency with the explicit security contract the mature surfaces carry.

This is consistent with the SUGGESTION finding made for education/skills/teach/context/lessons.md in the previous security review; neither batch has been addressed.


Supply-chain / workflow / secrets: No workflow files touched, no action pins changed, no secrets or tokens added, no new external dependencies introduced.
| Branch

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0279e058eb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread plugins/architecture/skills/improve/actions/deepening.md Outdated
Comment thread plugins/education/skills/teach/context/lessons.md
kyle-sexton and others added 2 commits July 29, 2026 13:12
Resolve the planning plugin's version and changelog conflicts by keeping
both sides: main's 0.27.0 draft-goal-condition work and this branch's
ephemeral-tier migration, renumbered to 0.27.1 on top of it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TkLLDcB8Ht2tm3j688F5ZP
… identity into HTML lessons

Two review findings, both defects this branch introduced.

The ephemeral tier's rule 1 named the temp root but not the template's
shape, and two adopters independently wrote `<prefix>-XXXXXX.html`. BSD
`mktemp` on macOS substitutes only trailing Xs, so neither the deepening
report nor the education primer could be created there at all. Rule 1 now
constrains the shape, and both producers take the `-d` run-directory form
with a fixed filename inside — which keeps the placeholders trailing while
still yielding a meaningful `.html` name, and is the form the planning
interview already used. The row's "temp file **or** directory" wording is
what makes this a conforming answer rather than an exception.

The slug-collision guard lost its identity source when `lesson.html` was
allowed to replace `lesson.md`: the guard compares an existing slice's
recorded raw concept name, and that name lived only in the Markdown
`**Concept:**` line, so `C++` and `C#` (both normalizing to `c`) could
silently share a slice. `lesson.html` now must carry
`<meta name="concept">`, and the rule names the marker per format so the
guard no longer depends on the lesson's extension.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TkLLDcB8Ht2tm3j688F5ZP
@claude

claude Bot commented Jul 29, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 5m 1s —— View job


PR Review — #1675 (commit 35ffa67)

  • Gather context and read changed files at HEAD
  • Review 35ffa67 — trailing XXXXXX fix and concept identity in HTML lessons
  • Check resolution of previously open threads (stale rule ref, adhd mktemp form)
  • Prepare two remaining fixes (blocked on 403 push — see below)
  • Post final review

What commit 35ffa67 resolved

Codex P1 — BSD mktemp portability (flagged on commit 0279e05)

Both architecture (actions/deepening.md) and education (context/lessons.md) had independently written mktemp "${TMPDIR:-/tmp}/<prefix>-XXXXXX.html" — a form that GNU mktemp handles but BSD mktemp (macOS) cannot, because BSD substitutes only trailing Xs and the .html suffix comes after them. Both adopters now use mktemp -d "${TMPDIR:-/tmp}/<prefix>-XXXXXX" and write a fixed filename inside the run directory. The contract's rule 1 is updated to document the trailing-Xs constraint and name the -d form as the portable answer when a meaningful filename is wanted. ✅

Codex P2 — Concept identity survives HTML lessons (flagged on commit 914452f)

Allowing lesson.html to replace lesson.md removed the slug-collision guard's only identity source: SKILL.md "Path resolution rules" reads an existing slice's recorded raw concept name before reusing its slug directory, and that name lived solely in the Markdown **Concept:** line. With an HTML lesson substituted in, C++ and C# (both normalizing to c) could silently share one slice. lesson.html must now carry <meta name="concept" content="<raw concept name>">, and SKILL.md names the marker per format (Markdown **Concept:** line / HTML <meta name="concept">) so the guard no longer depends on the lesson's extension. The CHANGELOG records both the filename-rule fix and the identity fix separately. ✅


Two fixes prepared locally — push blocked (403)

Two items remain open from prior review cycles. I staged and committed them as 53c31cf locally but the GitHub Actions token does not have write permission to this repository (consistent with every prior run). The diffs are exact — apply them manually before merge.


1. Stale rule reference — flagged in five consecutive review cycles

docs/conventions/topic-docs/README.md line 156:

cleanup semantics reopens rule 2, and the change lands here as a recorded changelog entry.

When this trigger was written (469e980), "Never the session scratchpad" was rule 2. Commit ff1f661 inserted a new rule 2 ("The lifetime outlives the call") and shifted the scratchpad to rule 3. The trigger now points a reader to the lifetime rule, not the scratchpad rule. A versioned scratchpad interface would affect both rule 3 (prohibition lifts) and rule 2 (lifetime is currently stated as outliving the session because no documented cleanup mechanism exists — a session-scoped scratchpad with cleanup guarantee changes that).

-cleanup semantics reopens rule 2, and the change lands here as a
+cleanup semantics reopens rules 2 and 3, and the change lands here as a

Fix this →


2. adhd SKILL.md — portable mktemp form not carried through

plugins/adhd/skills/clarify/SKILL.md line 130:

Write any local HTML file to the ephemeral tier — one file created through the platform's temp API — and hand back that path.

Commits 0279e05 and 35ffa67 updated the contract rule and both the architecture and education adopters to use mktemp -d "${TMPDIR:-/tmp}/<prefix>-XXXXXX". The adhd SKILL.md was not updated in either commit — it still says "the platform's temp API" without naming the form or the trailing-Xs constraint. A producer reading only the SKILL.md (which is the intended self-contained surface) could write mktemp clarify-XXXXXX.html, which creates the file in the current working directory on GNU and fails entirely on macOS.

-Write any local HTML file to the **ephemeral tier** — one file created through
-the platform's temp API — and hand back that path.
+Write any local HTML file to the **ephemeral tier** — one file per run, via the
+platform's temp primitive: on Unix, create a run directory with
+`mktemp -d "${TMPDIR:-/tmp}/clarify-XXXXXX"` and write `view.html` inside it
+(the `XXXXXX` placeholders must be trailing — BSD `mktemp` on macOS substitutes
+only trailing Xs, so a `…-XXXXXX.html` file template is not portable); on Windows,
+a user-scoped temp under `%LOCALAPPDATA%\Temp`. Hand back the file path.

Also update plugins/adhd/CHANGELOG.md [0.3.1] entry to record the mktemp form correction:

-  delivery mechanism, so it must stay readable when the reader opens it. See
+  delivery mechanism, so it must stay readable when the reader opens it. Its
+  `mktemp` invocation now takes the `-d` run-directory form
+  (`mktemp -d "${TMPDIR:-/tmp}/clarify-XXXXXX"`, with `view.html` written
+  inside): BSD `mktemp` on macOS substitutes only **trailing** `XXXXXX`, so the
+  `…-XXXXXX.html` file-template form is not portable. See
   `docs/conventions/topic-docs/README.md` §"The ephemeral tier".

Fix this →


What's correct in 35ffa67

  • All files that direct runtime behavior for the two Codex findings are updated consistently: README rule 1 names the trailing-Xs constraint, both adopters (deepening.md, lessons.md) switch to the -d form, and SKILL.md "Workspace layout" names the <meta name="concept"> marker so the guard is format-agnostic
  • The CHANGELOG entries for both the architecture plugin ([0.4.2]) and education plugin ([0.5.3]) record the mktemp form correction and concept-identity fix separately and accurately
  • The planning interview (loop.md) was already using mktemp -d "${TMPDIR:-/tmp}/interview-XXXXXX" — the correct trailing form — so it needed no update
  • The four planning skills that route HTML via the binding reference doc (brainstorm, design, plan, prd) have no inline mktemp instructions to fix

Full thread status

Thread Status
P2: Keep returned HTML alive until user can open it ff1f661
P2: Update operational deepening step to use ephemeral tier ff1f661
P2: Stop promising session cleanup without mechanism bae26aa
P2: Route CLAUDE_CODE_TMPDIR into mktemp bae26aa — correctly documented it cannot be observed
P2: Implement promised temp-root userConfig bae26aa — reworded as form constraint
P2: Correct scratchpad adoption history 7d9bac5
P2: Route planning's per-round HTML to ephemeral tier 5a80d2d
P2: Migrate education SKILL.md 914452f
P2: Add lifetime question to tier decision 0279e05
P2: Require mktemp to select the temp directory (README + architecture + education) 0279e05
P2: Define canonical filename for HTML lessons 0279e05
P2: Place four unresolved planning HTML producers 0279e05
P1: BSD mktemp portability — XXXXXX must be trailing 35ffa67 — architecture and education adopters switch to -d form
P2: Concept identity survives HTML lessons (<meta name="concept">) 35ffa67 — guard now format-agnostic
Third review #1: "and removes it" contradicts rule 2 bae26aa
Third review #2: Re-derivation trigger references wrong rule number ⚠️ Prepared as 53c31cf, push blocked (403)
0279e05 gap: adhd SKILL.md still says "temp API" without naming portable form ⚠️ Prepared as 53c31cf, push blocked (403)
Security: HTML surfaces lack explicit HTML-escape requirement (education, planning) ℹ️ Non-blocking, carry to follow-up if desired

Verdict

Two one-line fixes before merge (both prepared in local commit 53c31cf; push 403). Everything else resolved. The 35ffa67 BSD portability fix and concept-identity fix are correct and complete. Once the two open items are applied, the PR is ready to merge.
| Branch

@claude

claude Bot commented Jul 29, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 3m 36s —— View job


Security Review — PR #1675 (commit 35ffa67)

  • Gather context and understand request
  • Fetch PR diff at HEAD (35ffa67)
  • Read changed files
  • Analyze for security issues (logic flaws, injection surfaces, trust boundaries, secrets handling)
  • Post findings

No CRITICAL or IMPORTANT security vulnerabilities found.

Commit 35ffa67 touches 7 files: 3 markdown docs (convention doc + 2 changelogs), 2 AI-facing instruction files, and 1 combined doc/instruction file. No executable code, no GitHub Actions changes, no secrets, no new dependencies.


Trust-boundary and data-flow analysis

The two files directing agent runtime behavior are the only meaningful security surface in this commit.

plugins/architecture/skills/improve/actions/deepening.md L39

The mktemp form changes from mktemp "${TMPDIR:-/tmp}/deepening-review-XXXXXX.html" to mktemp -d "${TMPDIR:-/tmp}/deepening-review-XXXXXX" + write report.html inside. The -d flag creates the directory with mode 0700 on Unix, meaning only the creating user can read, write, or list its contents — the report file inside inherits those restrictive access controls, making it strictly narrower than a file in the shared /tmp root. The template string remains fully static (no user-controlled or codebase-derived components), so no path-injection surface is introduced. The open/xdg-open/start step still operates on a mktemp-generated path, not on any user-supplied string.

Net change: security improvement.

plugins/education/skills/teach/context/lessons.md L51–53

Two changes here:

  1. lesson.html must carry <meta name="concept" content="…"> — The instruction correctly says to HTML-escape the raw concept name in the attribute value ("HTML-escape it, do not slugify it"), which prevents attribute-injection from names containing ", <, or >. The concept name is the user's stated learning goal, not codebase-extracted content, so it is not an attacker-controlled surface in the conventional sense. Even in codebase mode, the concept comes from user intent (e.g. "C++ templates"), not from arbitrary strings read out of the repository. The <meta> element carries no script-execution semantics regardless, but the escape requirement is still correct and should be followed.

  2. Primer HTML changes to -d form — Same analysis as the architecture change above: mktemp -d "${TMPDIR:-/tmp}/primer-XXXXXX" creates a 0700 directory; primer.html inside it inherits the access restriction. The "Self-contained, no remote fetch" and "No secret leakage" constraints are preserved.

Net change: security improvement on both fronts.

docs/conventions/topic-docs/README.md L88–97

The rule now documents that XXXXXX placeholders must be trailing, and explicitly records that a non-portable template landing in the current working directory — i.e., the consumer's repository — was the defect. This closes the CWD-landing risk the prior form carried on BSD systems. Documentation-only; no security consequence beyond closing the gap for future implementers.

Net change: security improvement for future producers.


Persistent open finding — SUGGESTION (carried from prior review cycles)

Severity: SUGGESTION
Confidence: PLAUSIBLE

Four planning HTML producers still lack an explicit HTML-escape requirement.

This finding was raised in the security reviews of commits 914452f and 0279e05 and is not addressed in 35ffa67. The four planning SKILL.md files — brainstorm/SKILL.md, design/SKILL.md, plan/SKILL.md, prd/SKILL.md — route HTML to the ephemeral tier without the explicit HTML-escape requirement the two mature surfaces carry:

  • plugins/adhd/skills/clarify/SKILL.md L166–172: "Escape copied text before it becomes HTML… closes an injection vector: the artifact you are clarifying may be untrusted."
  • plugins/architecture/skills/improve/research/deepening/html-report.md L5: "Escape all codebase-derived text… must be HTML-escaped (&, <, >, ", ') before embedding."

All four planning views embed codebase-derived content (file paths, module names, candidate descriptions, PRD content). TypeScript/Go/C++ identifiers commonly contain <T>, &&, and template literal syntax; if any reach these HTML views unescaped, page rendering breaks. Practical exploitability is low — the file is opened only by the invoking user — but the gap is an inconsistency with the security contract the other surfaces carry.


Supply-chain / workflow / secrets

No workflow files touched, no action pins changed, no secrets or tokens added, no new external dependencies introduced.


| Branch

@kyle-sexton
kyle-sexton merged commit ecae344 into main Jul 29, 2026
33 checks passed
@kyle-sexton
kyle-sexton deleted the fix/1635-topic-docs-ephemeral-tier branch July 29, 2026 17:26
kyle-sexton added a commit that referenced this pull request Jul 29, 2026
… now requires

#1675's lane resolved the open suffix question and wrote the answer into
`docs/conventions/topic-docs/README.md` rule 1: the `XXXXXX` placeholders
must be TRAILING, because BSD `mktemp` on macOS substitutes only trailing
Xs. So `<prefix>-XXXXXX.html` is not merely non-conforming -- the file
cannot be created at all there. GNU coreutils accepts it and this repo's
CI is ubuntu-24.04 throughout, so no gate here would have caught it.

Both HTML producers already take the `-d`-plus-fixed-filename form the
rule prescribes, so they conform as written and no invocation changes.
What changes is the justification: the prose said we merely "do not
depend on" the suffix working, hedging an unknown. The unknown is now
settled, so it says plainly that the suffix form is broken on macOS and
that the run directory is what preserves the `.html` name.

Merges origin/main to pick up that rule.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TkLLDcB8Ht2tm3j688F5ZP
kyle-sexton added a commit that referenced this pull request Jul 29, 2026
…act (#1708)

## Summary

A marketplace-wide audit classified **nineteen** ephemeral-file
producers against the topic-docs ephemeral tier. Fourteen were already
conforming or correctly classified in another tier. **Five violated the
contract**; this fixes them.

### `firecrawl` — the worst of them

Every scrape, search, and interact call wrote `/tmp/fc-<nonce>.<ext>`:

- **hardcoded literal `/tmp`** with a hand-rolled `date +%s%N` nonce
instead of the platform primitive, and **no Windows branch at all**
- **no cleanup anywhere** — one file per call, so a research-heavy
session left an unbounded pile behind. That is the footprint rule's
exact failure case, and the contract is explicit that nothing documented
reclaims the OS temp tree.

Now uses `mktemp "${TMPDIR:-/tmp}/fc-…-XXXXXX"`, which works on every
platform — including Windows, where Git Bash resolves `/tmp` through its
mount to `%TEMP%` (by default under `%LOCALAPPDATA%\Temp`). Spill files
are self-consumed, so they are removed after the `Read`, with the one
exception the contract requires: **when the user asked for the file
itself, the path is the deliverable and is never deleted.**

### The other four

- **`prototype/explore-directions`** — offered "an OS temp **or**
gitignored scratch location": a non-deterministic branch whose second
half also puts the file *inside the repository*, against the tier's
never-in-the-repo rule.
- **`visualization/visualize`** — wrote a local HTML file and handed
back its path with **no placement rule at all**, while its sibling
skills all carry one.
- **`event-storming/simulation`** — `{system_temp}` was never bound to a
platform primitive anywhere in the file. Its delete-vs-archive split was
already sound and is untouched.
- **`context7/lookup`** — `> /tmp/nextjs-router.md`, the same class as
firecrawl but in an illustrative pipe example rather than a mandated
rule. Fixed for consistency, and the example now echoes the generated
path in the same call: the docs output is redirected, so without the
echo a following `Read` has nothing to locate the randomly named file
with.

### On the wording, stated accurately

An earlier draft of this description claimed the new paragraphs were
taken from existing exemplars and therefore added no new phrasing.
**That claim was wrong**, and an exemplar-fidelity audit caught it. What
the diff actually does:

- The `visualization` and `prototype` paragraphs borrow their
*mechanics* from `architecture/improve/actions/deepening.md` (the
Windows path, the open-commands, "report the absolute path") and the
phrase "never lands in the consumer's repository tree" from
`adhd/clarify/SKILL.md` — but they are near-duplicates **of each
other**, i.e. one new phrasing used twice, not two independent
derivations. They no longer inherit that exemplar's `mktemp` flag
choice; see the portability section below.
- Both add two normative claims that appear in **no** exemplar: "one
file per run" and an explicit "do not delete it". These come from the
**tier contract itself**, which is their correct source; the earlier
attribution to the exemplars was the error.
- The `event-storming` paragraph tracks no exemplar. Its framing is new
phrasing, structurally justified because that file needs a whole session
*directory* rather than a one-shot file.

### Session directory is created, not merely named

Review flagged this as a P1, and it is a real exposure rather than a
style point. `event-storming/simulation` composed its session directory
path from the session id and created it normally. On a multi-user POSIX
host with `TMPDIR` unset, `${TMPDIR:-/tmp}` falls back to the shared,
world-readable `/tmp` — so a predictable name both exposes the persona
and session Markdown to every local user (a normally-created directory
lands at 0755 with 0644 files inside) and lets any of them pre-create
the path, so the workshop writes into a directory someone else owns.

The reference now **creates** the directory with a secure primitive
instead of naming it: `mktemp -d
"${TMPDIR:-/tmp}/eventstorming-session-XXXXXX"` on POSIX/Git Bash, and
`New-Item -ItemType Directory` under a `[IO.Path]::GetRandomFileName()`
component in the per-user `$env:TEMP` on Windows PowerShell. The random
component defeats pre-creation and POSIX `mkdtemp` mandates mode 0700,
which gates traversal into the directory regardless of the modes of the
files inside it — so the file modes need no separate change. The
delete-vs-archive cleanup protocol is untouched, and the two downstream
sites that restated the old path formula now refer to the path the
primitive returned.

### Portability — fixed here, not deferred

An earlier draft of this description shipped `mktemp --tmpdir` and
`mktemp -t`, and recorded the gap as a known limitation to file a
follow-up for. Review caught it. It is fixed in this PR rather than
deferred.

**A second review pass then caught the *justification* being wrong, and
that correction matters more than the first.** This description — and
six sites in the diff — asserted that `--tmpdir` is "absent on BSD".
**That is false.** The [FreeBSD
`mktemp(1)`](https://man.freebsd.org/cgi/man.cgi?query=mktemp&sektion=1&format=html)
synopsis and the macOS/Xcode page both read `mktemp [-d] [-p tmpdir]
[-q] [-t prefix] [-u] template ...` — BSD has the directory flag. (The
claims below are kept to the short `-p` spelling, which is what both
synopses show verbatim; the long `--tmpdir` form is GNU's, and BSD
spells it inconsistently across versions.) The "absent on BSD" claim was
inherited from an existing comment in this repo and repeated as if
verified; it was not.

The real hazard is worse than absence, which is why the fix itself
stands unchanged. The flag exists in both dialects and **means different
things**:

- **GNU** treats the positional template as relative to that directory
and lets the flag beat `TMPDIR`. Its own manual deprecates `-t` on
exactly that ground: "the use of `-p` without `-t` offers better
defaults (by favoring the command line over `TMPDIR`)".
- **BSD/macOS** consult it only as a fallback **for the `-t` flag** when
`TMPDIR` is unset — "If the `-p` option is set, then the given *tmpdir*
will be used if the `TMPDIR` environment variable is not set." So
`TMPDIR` wins instead, and with a bare positional template and no `-t`
the flag does nothing at all.

So `mktemp --tmpdir visualize-XXXXXX.html` does not *fail* on macOS. It
resolves the template against the **current directory** and silently
writes into the consumer's repository — the precise outcome the
ephemeral tier's never-in-the-repo rule exists to prevent, and a silent
wrong answer rather than a loud one. BSD's `-t` also takes a *prefix*
rather than a template, so the two dialects produce different filenames
from the same argument.

Every call site in this diff therefore carries the temp root in the
**positional TEMPLATE** argument, which neither dialect reinterprets:

```bash
mktemp "${TMPDIR:-/tmp}/fc-scrape-XXXXXX"
mktemp -d "${TMPDIR:-/tmp}/visualize-XXXXXX"
```

The *form* is still the one **this repository already mandates for shell
scripts** — `scripts/shell-portability-tokens.txt` lints `mktemp -p` and
`mktemp --tmpdir` out of changed `**/*.sh` files and points at the
positional TEMPLATE replacement (#1527) — and that gate scanning `.sh`
only is exactly how these skill *documents* drifted from it. Note that
the gate's own inline comment carries the same "BSD does not implement"
error this PR just corrected in its own prose; fixing that comment is
#1544's lane, not this PR's, so it is deliberately untouched here.

The two skills that hand back an HTML file (`visualization/visualize`,
`prototype/explore-directions`) create a private run directory with
`mktemp -d` and write a fixed-name page inside it, rather than asking
`mktemp` for a template with a `.html` suffix *after* the `XXXXXX`.

**That question is now settled — and had it gone the other way this PR
would have shipped a macOS-broken instruction.** When these producers
were written the suffix form was an open risk (#1709 raised it). #1675's
lane has since resolved it against primary sources and written the
answer into the tier contract itself:
`docs/conventions/topic-docs/README.md` now requires the `XXXXXX`
placeholders to be **trailing**, because BSD `mktemp` on macOS
substitutes only trailing Xs — so `<prefix>-XXXXXX.html` is not merely
non-conforming, the file **cannot be created at all** there. GNU
coreutils accepts it and this repo's CI is `ubuntu-24.04` throughout, so
no gate here would ever have caught it.

Both producers already take the `-d`-plus-fixed-filename form that rule
prescribes, so they conform as written. The surrounding prose is
upgraded from "we do not depend on the suffix working" to stating
plainly that it does not.

**On verification, stated plainly.** GNU coreutils 8.32 under Git Bash,
executed in this environment: `mktemp -d
"${TMPDIR:-/tmp}/es-session-XXXXXX"` → `/tmp/es-session-cmjdnl`, exit 0.
(An earlier probe here also showed `mktemp
"${TMPDIR:-/tmp}/visualize-XXXXXX.html"` succeeding on GNU — that
success is exactly the trap the trailing-X rule now closes, since the
same command fails on macOS.) The PowerShell branch was executed too
(PowerShell 7.6.4, Windows 11): it creates the directory under
`%LOCALAPPDATA%\Temp` and returns its full path. **BSD/macOS is not
executable from here**, so every BSD claim above is quoted from the
FreeBSD and macOS man pages rather than run — which is the discipline
whose absence produced the "absent on BSD" error in the first place. The
0700 mode claim likewise rests on POSIX `mkdtemp`, not on this
environment (Git Bash reports 0755 because MSYS emulates POSIX modes
over Windows ACLs), and is now scoped to POSIX in the skill text rather
than stated flatly across both platforms.

What is still **not** safe, and is avoided everywhere in this diff: a
**bare** `mktemp` with a relative template resolves against the *current
directory*, so `mktemp -u fc-test-XXXXXX` run inside a repo returns
`fc-test-tdvDNS` — a file in the consumer's tree, against the tier's own
rule.

### Explicitly left alone

The audit confirmed these correct, and changing them would be
regressions: `quiz-me` and `ai-briefing` (machine state), `audit-pass`
(machine-state findings store), `course-digest` and `youtube-digest`
(correct Ephemeral/Memory/Machine-state split, verified against the
actual `os.tmpdir()` library code), `running-retro` (machine state by
design — the observer outlives the session), `machine-health` (durable
report archive), `pressure-test` and `prototype/context/discipline.md`
(in-repo throwaway *source code*, a deliberate pattern outside this
contract), and the `work-items` / `source-control` `mktemp` + `rm -f`
spill-file pattern (rule 2's permitted self-consumed exception).

## Test plan

Run from the branch worktree against `origin/main`:

- `scripts/check-changelog-parity.sh --check-bump origin/main` —
**pass** (all five plugins bumped with matching `## [<version>]`
entries)
- `scripts/check-contract-slice-prune.sh --check-diff origin/main` —
**pass**, no path under `docs/topics/`
- `scripts/check-skill-portability.sh origin/main` — **pass**, no
unexcused coupling tokens
- `markdownlint-cli2` over every changed Markdown file — **pass**, 0
errors
- `mktemp` forms exercised directly in this environment (GNU results and
the BSD caveat quoted above)

- `plugins/firecrawl/skills/update/scripts/update.test.sh` — **17/17
pass** after the `update.sh` temp-dir change
- `scripts/check-shell-portability.sh origin/main` — **pass** on the
changed shell file

One executable path changed in this PR: `update.sh`'s run-directory
creation, covered by the 17 checks above. Everything else is instruction
text in skill bodies and changelog prose, where no test suite applies.
CI confirms the full gate set.

Review findings from the adversarial passes have been addressed on this
PR, not deferred:

- the P1 session-directory exposure (predictable name in shared `/tmp`,
now created by `mktemp -d`);
- the non-portable `mktemp` flag form across every touched call site,
plus `skills/update/scripts/update.sh`, whose `-d -t` produced a
differently-named run directory on BSD (all 17 of its checks still
pass);
- the missing path echo in the `context7` example — and the same defect
in the new `mktemp -d` snippets, whose randomly-named directory is
unrecoverable in the following tool call without it;
- the command-specific wording of the `firecrawl` cleanup exception, now
genuinely command-agnostic rather than a closed seven-command list that
would need editing whenever a command is added;
- the false "`--tmpdir` is absent on BSD" premise at six sites,
corrected above against the FreeBSD and macOS man pages;
- the concurrent-session-safety bullet that still asserted the retired
`eventstorming-session-{id}` formula as the anti-collision mechanism;
and
- the 0700 claim, now scoped to POSIX `mkdtemp` rather than asserted
across both platforms.

## Related

**Closes #1700** — the ephemeral-producer sweep. This changed while the
PR was open: #1700 named two producers (`planning:interview` round
tables, `education:teach` lesson HTML), each gated behind an open design
question, which is why an earlier revision of this description said the
issue was deliberately *not* closed here. #1675's lane has since
resolved both — verified in merged `main` rather than taken on report:
`planning/skills/interview/context/loop.md` now creates one `mktemp -d
"${TMPDIR:-/tmp}/interview-XXXXXX"` run directory per interview, and
`education/skills/teach/context/lessons.md` took the outcome #1700
explicitly anticipated ("it was never ephemeral, fix the label") —
workspace lesson HTML is now documented as machine state, with only the
`primer` page migrated to the ephemeral tier. With those two answered
and the five here migrated, the sweep the issue describes is complete.

This is otherwise downstream conformance work from the ephemeral tier
contract added in #1635 (PR #1675). #1635 is closed by that PR, not this
one, so it carries no closing keyword here.

**#1709 stays open** and is deliberately not closed by this PR. Its
portability question is answered for these five call sites, but its
acceptance criteria also require the chosen form to be recorded
normatively in `docs/conventions/topic-docs/README.md` and applied to
**every** remaining `plugins/**` call site. At least two are outside
this diff and still on the old forms:
`architecture/improve/actions/deepening.md` (the origin of the
`--tmpdir` choice) and
`claude-config/skills/audit/scripts/fix-plugin-drift.sh` (`mktemp -t`).
That migration and the normative record remain #1709's, and it carries
`needs-human` / `status: needs-decision` for a reason.


Closes #1700

## Related

- Closes #1700 — the ephemeral-producer sweep. Its two named producers
were resolved by #1675's lane; these five are the remainder, so the
sweep completes here.
- Refs #1675 — the PR that introduced the ephemeral tier row this sweep
enforces, and whose lane added the trailing-`XXXXXX` constraint these
producers already satisfy.
- Refs #1709 — the mktemp portability follow-up; answered for these call
sites, left open for the normative record and the marketplace-wide
migration.
- Refs #1544 — owns the `scripts/shell-portability-tokens.txt` comment
that still carries the "BSD does not implement" framing corrected here;
deliberately untouched by this PR.

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

topic-docs: tier table has no ephemeral row, so two plugins invented a scratchpad-first answer

1 participant