Skip to content
claudewheel.deletion_checklist
Edit
On this page

The screen deletion shows before it removes anything: every live process holding the profile, with the daemon pre-ticked, stopped in place as the user confirms.

#claudewheel.deletion_checklist

#claudewheel.deletion_checklist

Present everything holding a profile, and stop exactly what the user ticks.

Deleting a profile directory does not free it. Every surviving Claude Code process still carries CLAUDE_CONFIG_DIR pointing at the path that was just removed, and any invocation of the client -- including a read-only status query -- recreates its configuration directory before doing anything else, so the deleted profile reappears as a husk. That is why deletion asks first: rather than promising a completeness it cannot deliver, it shows every live process registered under the profile and lets the user decide, per row, which ones to stop.

What is ticked before the user touches anything -----------------------------------------------

The daemon and its workers, and nothing else. Those are claudewheel's own consequence -- the profile's daemon exists because the profile was launched -- while a background job is work someone started deliberately, so it is listed, never pre-selected. Nothing is stopped without an explicit tick.

The screen does not close on confirmation: each ticked row's state line goes from a green running to a red stopped as its process really goes, and escape leaves afterwards.

Stop-then-remove, never the reverse -----------------------------------

The caller stops holders before removing the directory. Reversing that order does not merely risk a husk, it guarantees one: the daemon-stop command is itself an invocation of the client, so run against an already-deleted profile it recreates the directory it was asked to shut down. This ordering will look wrong to a future reader -- "stop the thing, then delete its home" reads like an optimisation -- so it is stated here and at the call sites.

#Holder

One live process holding the profile, and what the screen knows about it.

#row

python
def row(self) -> ListRow

This holder as a row of the shared list component.

#ChecklistOutcome

What the screen decided and what it managed to stop.

still_holding is every holder whose process is really still up when the screen closes -- typically the ones left unticked plus any whose stop did not take, but it is re-probed rather than subtracted from the snapshot, so a holder that exited on its own while the screen was open is not in it. It is what lets the deletion say the directory may come back instead of claiming it is gone.

#gather_holders

python
def gather_holders(config_dir: Path) -> list[Holder]

Every live process registered under config_dir, memory included.

Resident memory is measured for all of them in one call, so opening the screen spawns one ps rather than one per row.

#stop_order

python
def stop_order(holders: Sequence[Holder]) -> list[Holder]

The ticked holders, supervisor first.

A worker stopped before its supervisor can simply be put back; stopping the supervisor first makes the rest stay stopped.

#still_the_registered_process

python
def still_the_registered_process(record: SessionRecord) -> bool

True when record's pid still names the process that registered it.

The checklist gathers its holders once and then waits on a human, so every pid it holds is a snapshot of unbounded age by the time anything acts on it. The kernel recycles pid numbers freely, so "the pid exists" answers the wrong question -- this asks the phantom filter's question, comparing the kernel start token the record recorded against the token the pid carries now.

#Stopper

Stops one holder by whichever mechanism its kind calls for.

The daemon-stop command is issued at most once however many daemon rows are ticked: it shuts the supervisor down, and a second invocation would only re-create the config directory it was pointed at.

#stop

python
def stop(self, holder: Holder) -> bool

Stop holder and wait for it to go. True when it really went.

Identity is re-checked immediately before anything is signalled, and again on every poll of the wait: a pid whose start token no longer matches belongs to a different process now, so the one the row names is already gone. That counts as stopped -- the profile is not held by it -- and nothing at all is signalled, because the signal would land on a stranger.

A preview issues the stop, so the would-do log gets it, but does not wait on it: the chokepoint recorded the signal instead of sending it, so the process is still there and the poll could only burn its whole timeout per ticked row before reporting a failure that never happened.

#run_checklist

python
def run_checklist(holders: list[Holder], *, profile_name: str, config_dir: Path, binary: Path, env: Mapping[str, str], theme: ThemeColors, terminal: Terminal, now_ms: int, identity: SessionIdentity | None=None) -> ChecklistOutcome

Run the checklist over holders and return what it decided.

Two phases in one screen. While selecting, up/down move, space toggles and enter confirms; escape cancels and stops nothing at all. After confirming, the screen stays where it is and each ticked row is stopped in turn, its state line redrawn as it goes, and the loop then waits for a key before handing the terminal back.

Search