fix(stop): the exit-4 refusal names where the holder may be (Issue #31, Mac review)

The refusal pointed at ~/.claude/sessions/ for every agent, whatever
CLAUDE_CONFIG_DIR said, and never mentioned a non-agent process naming the
uuid on its command line. The in-use CLI now prints, on stderr while the
conversation is in use, the adapter's in_use_hints: for every adapter
`ps -axww | grep <uuid>`, and for claude its real sessions/ dir first. stop
prints them under its refusal, and says "records preserved" instead of
"no state was modified": the gate runs after the kill chain, so the session
itself may already be down. SKILL.md's exit 4 row says the same; it is not
a code block, so no inventory hash moves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
GodopuandClaude Opus 5.5 committed 2026-10-16 03:25:10 +00:00
1 parent af8ed397f6
commit e156179822
6 files changed
+25 -6

No files matched your search

+6 -1
View File
@@ -155,7 +155,8 @@ def main():
# in-use <agent> <uuid> [workspace] [--wait <sec>] (Issue #31)
# The purge CLI's own in-use verdict, for stop_session.sh to ask
# before it purges. Re-checks every 0.25 s for up to --wait seconds.
# Prints {"agent", "in_use"}. Exit 0 free; 4 still in use; 1 bad args.
# Prints {"agent", "in_use"}, and while in use where to look on
# stderr. Exit 0 free; 4 still in use; 1 bad args.
args = sys.argv[2:]
wait = 0.0
if '--wait' in args:
@@ -181,6 +182,10 @@ def main():
time.sleep(0.25)
in_use = adapter.conversation_in_use(uuid, ctx)
print(json.dumps({"agent": agent_name, "in_use": in_use}))
if in_use:
print(" What may hold it:", file=sys.stderr)
for hint in adapter.in_use_hints(uuid, ctx):
print(f" {hint}", file=sys.stderr)
sys.exit(4 if in_use else 0)
else:
@@ -345,6 +345,10 @@ class ClaudeAgentAdapter(BaseAgentAdapter):
return True
return any(_session_file_live(data, pid) for _, pid, data in self._session_files(uuid, ctx))
def in_use_hints(self, uuid: str, ctx: DiscoveryContext) -> list:
return [f"a claude process whose session file under {os.path.join(_config_dir(ctx), 'sessions')}/ names it"] \
+ super().in_use_hints(uuid, ctx)
def purge_artifacts(self, uuid: str, ctx: DiscoveryContext) -> list:
# Best effort: never raises (stop runs this inside its registry
# transaction). What could not be removed is not listed here and
+5
View File
@@ -308,6 +308,11 @@ class BaseAgentAdapter:
"""True while a live process still references the conversation."""
return conversation_in_use(uuid, [self.artifact_path(uuid, ctx)])
def in_use_hints(self, uuid: str, ctx: DiscoveryContext) -> list:
"""Where to look for what holds the conversation, shown when a purge
is refused because it is in use."""
return [f"any process with it on its command line (an editor, a grep, ...): ps -axww | grep {uuid}"]
def spawn_spec(self, binary: str, session_uuid: str = "", use_wrapper: bool = False) -> str:
raise NotImplementedError