Generated from cat-harness/skills/conduct/conduct-core/deletion-requires-confirmation.md — do not edit here.

✎ Edit this page’s source

Deletion requires explicit confirmation

One rule, and it has no exceptions worth the word:

An agent does not remove a durable artefact on its own initiative. It reports what it would remove — with sizes, ages and what each one is — and waits for the user to say so.

“On its own initiative” is doing the work in that sentence. The rule is not about malice or about big deletions. It is about the gap between an agent decided this was no longer needed and a person decided it, and the whole point is that the second is cheap to obtain and the first is never recoverable from the outside.

And that is exactly why a WAIVER is not an exception to it. The owner may grant the deletion gate in advance — for a session, for a process run, for a named artefact class — and a grant of that kind is the person deciding, which is the side of the gap this rule protects. What is never permitted is the agent supplying the decision itself, under any name. A waiver carries the grantor, their verbatim words, one gate class, a scope and an expiry, and one missing field means there is no waiver and this rule stands unchanged: confirmation-waiver.md.

Two things a deletion waiver never reaches, and both are here rather than in the waiver skill because this is where a reader looks for them. It is never blanket — a grant over build artefacts is not a grant over content, and a scope that does not plainly cover the case in front of you does not cover it. And it never reaches a bean, because AGENTS.md’s never delete ANY bean is a prohibition rather than a confirmation anybody is owed; scrapped, with reasons, is always available, so there is nothing there to give back.

Why removing something is not the inverse of creating it

Creating a file and deleting one look symmetric and are not, for three reasons that all cut the same way.

A deletion destroys the evidence that it happened. A wrong file is visible and gets fixed; a removed one is absent, and absence looks exactly like “there was never anything here”. Nobody greps for a thing they do not know existed.

The person who pays is not the person who acted. A sibling session, a reviewer following a link, the next agent to pick up the thread — none of them were in the room, and none of them can tell your deliberate removal from an accident. AGENTS.md states the strongest instance of this for beans, and states the reason generally: “a scrapped bean records that something was considered and rejected, which is what stops the next agent re-entering the same dead end; a deleted one leaves a sibling unable to tell abandonment from accident.” That reasoning is not about beans. Substitute “staging preview”, “witness”, “library source” and it is unchanged.

Asking is nearly free and reverting often is not. The cost of confirming is one line in a report and one word back. The cost of a wrong deletion is whatever it took to produce the thing, plus the time somebody spends discovering it is gone, plus — in the worst and commonest case — never discovering it at all.

This skill is the general rule; AGENTS.md carries one instance of it. Where the two overlap, this is the source of truth and the AGENTS.md entry is a pointer. Do not restate the bean rule here as a second, competing rule — it is the same rule, applied to one store.

What counts as durable

The test is not “is it committed” and not “is it large”. It is:

Could somebody other than me want this later, and would they be able to tell it was removed on purpose?

If the answer to the first is yes and to the second is no, it is durable.

Durable — confirm before removing:

artefactwhythe non-destructive move
a bean (beans/defs/*.md)records that something was considered; ids are referenced from commits, issues and other beansset status: scrapped with the reasons; beans archive MOVES resolved ones
a todo (todos/items/*.md)it is a person’s outstanding work, not an agent’sask the person; an agent may neither close nor remove one
a staging preview (STAGING/<slug>/ on the publish branch)it is the artefact a human assesses a rendered change from — unless the PR merged, when the main site now shows the same thingmerged → removed automatically; the merge IS the confirmation (owner, 2026-09-20). Otherwise: while the PR is open, the staging:cleanup label applied by a person; once closed unmerged, a feature-staging.yml dispatch with cleanup_slug + a matching cleanup_confirm, since the label cannot reach a closed PR (bean w2g5)
a published page on the sitea URL somebody has linked or bookmarkedsay what would 404 and let the owner decide; a redirect is often the answer
a QA verdict or witness (test/results/**)the previous answer is what separates “this broke today” from “this has been broken since it was written”regenerate it in place; never remove the file to make a sweep clean. Its record is the commit-keyed entry on the qa-reports branch (arc 3fva); taking the committed copies off main is bean 5hox, on the owner’s explicit go, after the branch holds a hash-verified copy. An entry ON qa-reports is removed only by qa:prune’s declared retention, never by hand
a QA attestation (test/attestations/**)a judgement — agent, human or baseline pair — that no regeneration can reproducenever removed to clear a finding; a stale one is re-judged, and the new judgement replaces it through its writer
library / uploads contentevery knowledge-graph reference to a source resolves through library/; a file in uploads/ reads as absent to every consumer while still on diskmove it forward through the pipeline, or ask
a workflow instance (beans/workflows/*.json)it is where a running process got to; a sibling session is reading itcomplete or discard the instance through the engine, which records the fact
a branch, tag or ref somebody else pushedit is the only copy of work that is not on mainleave it; say it looks finished with

Not durable — rewrite or remove freely:

  • scratch files in a scratchpad or temp directory, and anything you created this session that nothing else has seen;
  • build output: _site/, _kg/, dist/, node_modules/, test-results/;
  • a generated artefact being rewritten in place by its own generator. A regenerator that overwrites its output is not deleting anything — the contract is that the file is derived. The distinction is in place: writing a fresh kg-qa sidecar over the old one is regeneration; removing a sidecar whose subject you could not load is a deletion, and a false pass besides.

Three cases sit on the line and all three resolve the same way — ask: a file you believe to be an orphan, a directory you believe to be empty, and a file whose only reference you have just removed. In each, the belief is exactly what would be wrong if you are wrong.

Establish which instance owns a node before you propose removing it. In a multi-instance layout a file that looks orphaned here may be declared from somewhere else. placement is the skill for that question; it is the same question in the other direction and does not need restating here.

What to do instead

Report, do not act. The report is the deliverable, and it is not a one-liner:

  • Name every artefact. Assert by naming, never by counting — “4 previews are orphaned” is a statistic and “STAGING/claude-d2kp-live-verdicts (36.8 MB, last deployed 11 days ago, PR #378 merged) matches no open pull request” is something a person can answer.
  • Give the size and the age, because that is what the decision turns on.
  • Say what is lost if it goes, and say it in terms of what would differ — a 404 on a link, a reviewer unable to compare, a sibling unable to tell abandonment from accident.
  • Recommend one option and mark it as your recommendation, then say what happens if they say nothing. This is the same frame interaction-modality §4.1 requires of every handed-over decision: context, options with their costs, recommendation, then the question. A reader must be able to answer without opening anything.

Prefer the move that keeps the record. Almost every case has one: scrapped over deleted, archive over remove, redirect over 404, regenerate over unlink, a label over a workflow step.

When the user does say so, do exactly that and no more. A confirmation to remove one thing is not a confirmation to remove the class it belongs to. If the scope is ambiguous, the answer is another question, not an inference.

A standing confirmation for a class is possible, and it is the owner’s to give — never an inference. The worked case is the staging preview cap. Owner ruling, 2026-10-02 (issue #1868): “cap the maximum number of previews (<= 10) and rotate old ones off.” That one sentence confirmed, in advance, every removal of the least recently updated preview beyond ten, so the stage job removes them without asking per preview. On 2026-10-04 the owner RE-SCOPED it — “Cap by size, not count”, budget “3gb” — and the class changed with it: the standing confirmation now covers the oldest previews beyond 3 GB in total, and no longer the eleventh. A class confirmation covers the class as the owner last stated it; changing the rule is theirs, and an agent that changes the code without that is inferring a confirmation. What the ruling does NOT waive is the record: each rotation still names the artefact, its age and its size — in the commit message, the job log and a removed render-log entry — and retires its staging-preview.json into STAGING/_retired/ rather than deleting it. And it is scoped exactly as given: it covers rotation over the cap, not any other removal of a preview, and it does not extend to _retired/ or anything else on the branch. See staging-review §”The cap”.

The failure this skill was written from — bean plj1

Not a hypothetical, and not an agent typing rm.

docs-site.yml published the site with peaceiris/actions-gh-pages@v4, keep_files: false, no destination_dir. That action’s own implementation does, at push time:

git clone --depth=1 --single-branch --branch gh-pages <remote> workDir
git rm -r --ignore-unmatch '*'      # everything on the branch, STAGING included
cp -R publish_dir/* workDir/

So every push to main that touched docs/ deleted every open pull request’s review preview. Measured on the real gh-pages history, 2026-09-19: three consecutive deploys removed 1, 3 and 1 previews, every one their parent commit carried.

Four things about it are worth carrying forward:

  1. Nobody decided it. There was no deletion step, no flag saying “remove previews”, no line anyone wrote with that intent. The shape of the workflow deleted them. A deletion nobody decided is still a deletion that requires confirmation — and since there is nobody to ask at push time, the answer is that the workflow must not be shaped that way.

    The policy changed on 2026-09-20 and this example is untouched by it. A merged PR’s preview is now removed without a label, because the merge is a person’s decision that the content belongs on main — a stronger confirmation than a label. plj1 deleted the previews of open PRs: work nobody had accepted, mid-review. Nothing in the new policy reaches an open PR, and the one-directory-at-a-time shape this bean forced on the job is unchanged. The lesson stands exactly as written.

  2. It overrode a written policy that said the opposite. feature-staging.yml removes a preview on PR close only with a staging:cleanup label, and otherwise posts “Staging preview retained … so reviewers can continue comparing”. One workflow’s implicit behaviour beat another’s explicit rule, and the explicit one was the one a human had read.
  3. It was completely silent. The staging deploy succeeded, the bot commented the URL on the PR, the check run was green — and an unrelated merge removed the artefact minutes later. A reviewer following the link got a 404 with nothing anywhere saying why. This is the first property in this skill: the deletion destroyed its own evidence.
  4. What it destroyed was the thing merge discipline depends on. AGENTS.md is explicit that a human cannot assess a rendered artefact from a description of it. The preview is the assessment.

Fixed in #377 by restoring STAGING/ into the publish directory before the push — which keeps both halves, since the main site is still a full replace so a removed page goes, while the previews are part of what is published.

The sequel, and why “no remedy” is its own hazard — bean w2g5

The same family, 2026-09-19, and it cuts the other way. cleanup in feature-staging.yml removes a preview only on a staging:cleanup label, and only on the pull_request_target: closed event — so once a PR is closed the label can no longer reach it, and re-running the old run replays a payload that still carries no label. Meanwhile staging-preview-orphans can only ever name a preview whose PR is already closed. The one remedy the finding documented was unreachable for every artefact it could ever name.

That is not a safe failure. It leaves a person holding a report with no sanctioned action in it, and the two unsanctioned ones are a hand-pushed gh-pages commit — this skill’s whole subject — or switching the check off. A policy of “confirm before removing” needs a removal that can actually be confirmed; otherwise the confirmation has nowhere to go. The fix was a workflow_dispatch path whose confirmation input repeats the slug, and which re-evaluates liveness at removal time rather than trusting a report that may be a day old.

And the check itself had the opposite defect at the same moment: it read “no open pull request” as “abandoned” and named a branch that had been committed to two minutes earlier. Reporting rather than acting is what kept that from becoming a deletion — but a report that names live work still invites one, which is why the list a person is asked to act on has to be as careful as the action itself.

Name the instrument that produced the list — bean pesg

A candidate list must say where it came from. If a committed check computed it, quote the check’s finding and name the check. If you built the list by hand, say that in as many words, so the person can see they are being asked to act on a derivation rather than on a measurement.

This is not bookkeeping. 2026-09-25, the same STAGING/ previews as plj1 above: bun run cat health reported one orphaned preview, named and sized. A review re-implemented the slug rule inline — [^a-zA-Z0-9]+, where feature-staging.yml preserves ., _ and - — reported three, and put that number to the owner, who authorised deleting “the 3 orphans”. Two of the three were live previews for open pull requests #1337 and #914, which a reviewer could have been reading. Nothing in this skill caught it: every rule here was obeyed. The artefacts were named, sized, aged and confirmed. The list was simply wrong, and no rule asked where it came from.

It was caught by measuring each directory’s size before deleting, noticing a name that looked like an open PR’s branch, and re-checking — that is luck wearing the clothes of diligence, and it is not a control.

The asymmetry is the whole point. A hand-derived list that is SHORTER than the instrument’s is a harmless miss: something survives that could have gone. One that is LONGER extracts a permission the instrument never justified, and this skill’s central assumption — that the person is choosing from a true list — fails silently. So the rule is not “re-derive carefully”; it is do not re-derive what a committed check already computes, which is goal-review rule 2’s qualification, stated there for the reviewing side.

stagingSlug was exported from schemas/staging.ts at the time, and test/health/checks.test.ts already asserted it differentially against the real sed pipeline. A correct, tested implementation was one import away. So the gap was never duplication in the code — it was that nothing required the list’s provenance to be stated.

Applying it to your own code

Two tests, both cheap, both derived from plj1:

Does anything you are writing remove something as a side effect of doing something else? A full-replace publish, a --prune, a cleanup step, a cache eviction, a git rm that globs. If so, name what it removes, in the code, and make sure a person chose it.

If it is a sweep, does it act or does it report? A checker that finds accumulation will be tempted to clear it. test/health/checks.ts is the worked counter-example in this repository: five checks, four of which are about artefacts piling up, and every action on every finding names something a person does. The daily workflow that runs them opens an issue and removes nothing.

Checklist

  • Is the thing durable by the test above? If unsure, it is.
  • Is there a non-destructive move — scrap, archive, redirect, regenerate, label? Prefer it, and say you preferred it.
  • Have you named each artefact, with its size and age and what is lost?
  • Does the list say which instrument produced it — or, if you built it by hand, does it say that? A hand-derived list longer than the check’s takes a permission nothing measured (bean pesg: 3 offered where health found 1, two of them live).
  • Have you given a recommendation, and said what happens on silence?
  • Did the user confirm this removal, rather than a similar one?
  • Does any code you wrote remove something as a side effect? Say so in the code, and check a person chose it.

Processes that run this skill

processstep(s) that name it
A sub-graph wants to leaveReport what would move — sizes, and what breaks; Authorise the extraction
A knowledge graph leaves for its own repositoriesReport what moves — sizes, what breaks; 9 · Authorise the extraction; 13 · Cutover: retire the parent’s copy
A sub-KG is staged in place, then leaves for its own repository6 · Report what would move, and how it undoes; 7 · Owner confirms: create the repository; 12 · Owner confirms: freeze the in-repo copy; 13 · Cutover: freeze the copy in the kept trashcan
Actor and role administrationRetire an actor — never delete one
Staging a feature branch preview, and taking it downRemove the preview AND append removed — one commit; Check the slug’s SHAPE and that the confirmation repeats it; Re-evaluate liveness AT REMOVAL TIME; Remove it AND append removed — one commit; Repeat the slug, exactly, to confirm
Is the repository itself healthy?Run the health checks, keeping the report either way
Board: relocate content to the trashcanName what will move, and where it lands; Say what moved and where it went

folio-assistant — code under the Apache License 2.0, documentation under CC BY 3.0. Generated schema and API references are rebuilt from source on every push to main.

deployed b08c961 · 2026-10-23T23:11:09Z