Run the script, show its listing as it is, propose a set, and wait for the
user's word. Speak in short sentences when proposing, when clarifying, after
deleting, and on every refusal or error. Read the JSON yourself for the
numbers; never read it, paths or exit codes to the user, and never retell the
listing in your own words.
Every sentence quoted below is a model of what to say, not text to copy out.
Say it in the user's own language, keeping the names, counts and reasons the
command gave and inventing none; a listing block is the one thing shown
exactly as the command printed it.
The cycle
Run the listing with the Bash description "List files left by
entrust."
F="$(mktemp "${TMPDIR:-/tmp}/entrust-cleanup.XXXXXXXX")"
CLAUDE_PLUGIN_DATA="${CLAUDE_PLUGIN_DATA}" node "${CLAUDE_SKILL_DIR}/../codex/scripts/cleanup.mjs" --list --json >"$F" && cat "$F" && echo "snapshot: $F"
mktemp gives each listing its own file. A name built from the shell's
$$ does not: two listings in one shell would share it, and a number from
the first would then be read against the second. Keep the snapshot path
from the last line; step 3 needs that exact path. Show the text
field to the user in one code block, unchanged. It is the listing: numbered
items, their sizes, when they last changed, whether each is suggested,
selectable by its number or kept, and why. Say nothing about the items
yourself. When it says the agent scan used Node's fallback temporary
directory, keep that warning in the block so the user sees that agent scratch
elsewhere may not have been found. An empty inventory is "I found no items
covered by this cleanup."
Propose in one sentence exactly what proposed holds, by those rows' names
and their total size: "I suggest deleting the temporary files for agent
u1-astra and 172 temporary directories from the lock tests, about 11 MB;
shall I?" When selectable holds numbers that are not in proposed, add
one sentence naming them: "Item 1, the 9 September 2026 cleanup, item 7,
the standalone report from run 42, and item 18, 43 saved conversations from
the tests, can go too if you say their numbers." Then wait. With nothing
suggested and nothing else selectable,
say "I have no cleanup to suggest; the listed items are being kept for the
reasons shown."
Map the answer to numbers yourself. "Yes", "yes please", "go", "go ahead",
"apply it" or "да" is exactly the numbers in proposed. Digits are those
numbers; an affirmative with digits adds them to the suggestion; "only" or
"instead" restricts to the digits alone. "All" or "everything": ask "Do you
mean the items I suggested, or the runs, standalone reports and saved
conversations as well?" and wait. "No": "I'll leave the listed items in
place." A question: answer
it, delete nothing. Silence: wait. A number that is not in selectable is
not yours to send — say " is being kept; the listing says why." and
leave it out. Then run, with the description "Delete the cleanup items the
user selected.":
CLAUDE_PLUGIN_DATA="${CLAUDE_PLUGIN_DATA}" node "${CLAUDE_SKILL_DIR}/../codex/scripts/cleanup.mjs" --delete --from "<SNAPSHOT>" <numbers>
Do not run the listing again between the user's word and this call: the
snapshot is what binds each number to what was shown, and an item that
changed since then is left in place and reported.
The command prints one paragraph per outcome, then the fresh listing.
Report every outcome it printed in your own message, keeping its names and
reasons and adding none —
"I deleted the temporary files for agent u1-astra and 172 temporary
directories from the lock tests, and left the 9 September 2026 cleanup in
place because it changed since it was listed." Then show the fresh listing
in a code block when anything remains, and say "I have no further cleanup
to suggest." only when its last lines say nothing is suggested and nothing
else is selectable.
Reading the result
Read the whole output even on exit 10: deletions and refusals occur together.
Use the listing's names; omit outcomes that did not occur.
| Result |
What to say and do |
| 0 |
every deletion the command reported, then the fresh listing |
| 1 |
"I could not remove because ; check it by hand." beside the confirmed deletions |
| 2, no data directory |
"Cleanup could not start because this session has no plugin data directory configured; nothing was deleted." |
2, relative TMPDIR |
"Cleanup could not start because TMPDIR is not absolute; nothing was deleted." |
| 2, a stale or unreadable snapshot |
"The list I showed you is no longer usable, so nothing was deleted; here is the current list." Then start again at step 1 |
| 2, invalid command |
"Cleanup could not start because the command was invalid; nothing was deleted." Correct the call |
| 10 |
each refusal in its own sentence, carrying the command's own reason: it changed since it was listed; it is being kept and cannot be chosen; it changed while it was being removed |
| no readable result |
"Cleanup did not return a readable result, so I cannot yet confirm what was deleted." Establish the outcome first |
What it never touches
Answers, managed worktrees and their ledger, write locks and the shared Codex
home are listed and never removed: the driver prunes answers and reconciles
the next two itself, and the last is shared by every agent. The data directory
of another copy of this plugin is the user's own to remove — when they ask
how, say "This command removes it." and show that row's command from
manual in its own block. The one directory this does not cover is the plugin's own under its previous
name, codex-delegate-<marketplace>: listed as such, proposed, removed by number like scratch, unless a
plugin of that name is still installed. The notCovered commands apply only to other
entries in the coordinator's temporary directory. Say "To list those entries
without removing them, run this command." and show notCovered.listCommand;
for removal, notCovered.removeCommand. An agent started under another
temporary root is outside the agent scan; its report is kept while
report.json is absent. The driver's private <state>/tmp
directories are guarded and not listed: the driver owns them, and this cleanup
never makes them removable.
Forward CLAUDE_PLUGIN_DATA as shown. The script uses
ENTRUST_STATE_DIR first, then CLAUDE_PLUGIN_DATA; setup follows the
sibling's One call. On the clone route both
commands need the codex skill linked beside this one; the installation
recipe links the two together.
1---2name: cleanup3description: Lists files left by entrust, suggests what to remove, and deletes the user's selection after approval.4license: MIT5---67Run the script, show its listing as it is, propose a set, and wait for the8user's word. Speak in short sentences when proposing, when clarifying, after9deleting, and on every refusal or error. Read the JSON yourself for the10numbers; never read it, paths or exit codes to the user, and never retell the11listing in your own words.1213Every sentence quoted below is a model of what to say, not text to copy out.14Say it in the user's own language, keeping the names, counts and reasons the15command gave and inventing none; a listing block is the one thing shown16exactly as the command printed it.1718## The cycle19201. Run the listing with the Bash description "List files left by21 entrust."2223 F="$(mktemp "${TMPDIR:-/tmp}/entrust-cleanup.XXXXXXXX")"24 CLAUDE_PLUGIN_DATA="${CLAUDE_PLUGIN_DATA}" node "${CLAUDE_SKILL_DIR}/../codex/scripts/cleanup.mjs" --list --json >"$F" && cat "$F" && echo "snapshot: $F"2526 `mktemp` gives each listing its own file. A name built from the shell's27 `$$` does not: two listings in one shell would share it, and a number from28 the first would then be read against the second. Keep the snapshot path29 from the last line; step 3 needs that exact path. Show the `text`30 field to the user in one code block, unchanged. It is the listing: numbered31 items, their sizes, when they last changed, whether each is suggested,32 selectable by its number or kept, and why. Say nothing about the items33 yourself. When it says the agent scan used Node's fallback temporary34 directory, keep that warning in the block so the user sees that agent scratch35 elsewhere may not have been found. An empty inventory is "I found no items36 covered by this cleanup."372. Propose in one sentence exactly what `proposed` holds, by those rows' names38 and their total size: "I suggest deleting the temporary files for agent39 u1-astra and 172 temporary directories from the lock tests, about 11 MB;40 shall I?" When `selectable` holds numbers that are not in `proposed`, add41 one sentence naming them: "Item 1, the 9 September 2026 cleanup, item 7,42 the standalone report from run 42, and item 18, 43 saved conversations from43 the tests, can go too if you say their numbers." Then wait. With nothing44 suggested and nothing else selectable,45 say "I have no cleanup to suggest; the listed items are being kept for the46 reasons shown."473. Map the answer to numbers yourself. "Yes", "yes please", "go", "go ahead",48 "apply it" or "да" is exactly the numbers in `proposed`. Digits are those49 numbers; an affirmative with digits adds them to the suggestion; "only" or50 "instead" restricts to the digits alone. "All" or "everything": ask "Do you51 mean the items I suggested, or the runs, standalone reports and saved52 conversations as well?" and wait. "No": "I'll leave the listed items in53 place." A question: answer54 it, delete nothing. Silence: wait. A number that is not in `selectable` is55 not yours to send — say "<name> is being kept; the listing says why." and56 leave it out. Then run, with the description "Delete the cleanup items the57 user selected.":5859 CLAUDE_PLUGIN_DATA="${CLAUDE_PLUGIN_DATA}" node "${CLAUDE_SKILL_DIR}/../codex/scripts/cleanup.mjs" --delete --from "<SNAPSHOT>" <numbers>6061 Do not run the listing again between the user's word and this call: the62 snapshot is what binds each number to what was shown, and an item that63 changed since then is left in place and reported.644. The command prints one paragraph per outcome, then the fresh listing.65 Report every outcome it printed in your own message, keeping its names and66 reasons and adding none —67 "I deleted the temporary files for agent u1-astra and 172 temporary68 directories from the lock tests, and left the 9 September 2026 cleanup in69 place because it changed since it was listed." Then show the fresh listing70 in a code block when anything remains, and say "I have no further cleanup71 to suggest." only when its last lines say nothing is suggested and nothing72 else is selectable.7374## Reading the result7576Read the whole output even on exit 10: deletions and refusals occur together.77Use the listing's names; omit outcomes that did not occur.7879| Result | What to say and do |80| --- | --- |81| 0 | every deletion the command reported, then the fresh listing |82| 1 | "I could not remove <name> because <reason>; check it by hand." beside the confirmed deletions |83| 2, no data directory | "Cleanup could not start because this session has no plugin data directory configured; nothing was deleted." |84| 2, relative `TMPDIR` | "Cleanup could not start because `TMPDIR` is not absolute; nothing was deleted." |85| 2, a stale or unreadable snapshot | "The list I showed you is no longer usable, so nothing was deleted; here is the current list." Then start again at step 1 |86| 2, invalid command | "Cleanup could not start because the command was invalid; nothing was deleted." Correct the call |87| 10 | each refusal in its own sentence, carrying the command's own reason: it changed since it was listed; it is being kept and cannot be chosen; it changed while it was being removed |88| no readable result | "Cleanup did not return a readable result, so I cannot yet confirm what was deleted." Establish the outcome first |8990## What it never touches9192Answers, managed worktrees and their ledger, write locks and the shared Codex93home are listed and never removed: the driver prunes answers and reconciles94the next two itself, and the last is shared by every agent. The data directory95of another copy of this plugin is the user's own to remove — when they ask96how, say "This command removes it." and show that row's `command` from97`manual` in its own block. The one directory this does not cover is the plugin's own under its previous98name, `codex-delegate-<marketplace>`: listed as such, proposed, removed by number like scratch, unless a99plugin of that name is still installed. The `notCovered` commands apply only to other100entries in the coordinator's temporary directory. Say "To list those entries101without removing them, run this command." and show `notCovered.listCommand`;102for removal, `notCovered.removeCommand`. An agent started under another103temporary root is outside the agent scan; its report is kept while104`report.json` is absent. The driver's private `<state>/tmp`105directories are guarded and not listed: the driver owns them, and this cleanup106never makes them removable.107108Forward `CLAUDE_PLUGIN_DATA` as shown. The script uses109`ENTRUST_STATE_DIR` first, then `CLAUDE_PLUGIN_DATA`; setup follows the110sibling's [One call](../codex/SKILL.md#one-call). On the clone route both111commands need the codex skill linked beside this one; the installation112recipe links the two together.