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:
1 parent
af8ed397f6
commit
e156179822
6 files changed
+25
-6
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ bash .agents/skills/multi-agent-mux-stop/scripts/stop_session.sh \
|
||||
| 1 | From the checks before the stop, nothing was changed: YAML not found, or the session is not registered. **The registry update after the stop also exits 1** (the row disappeared from the YAML during the run) |
|
||||
| 2 | Bad arguments (including the retired `--mode` / `--capture-id` / `--graceful`), or `--agent` omitted and not resolvable |
|
||||
| 3 | `--purge-conversation` without `--yes` |
|
||||
| 4 | Purge aborted: the herdr session, or an agent process still holding the conversation, survived the kill chain (nothing was changed) |
|
||||
| 4 | Purge aborted: the herdr session, or an agent process still holding the conversation, survived the kill chain. Records preserved: the registry row and the conversation files are untouched, though the session itself may already be down |
|
||||
| other | The registry update after the stop (YAML / DB write) failed and its code is passed through |
|
||||
|
||||
Exits 2 and 3, and exit 1 from the pre-stop checks, come before anything is stopped, so nothing was changed. On exit 1 from the registry update, or an `other` exit, the session may already be down while its row still says `running`: check with `multi-agent-mux-status`, then run the stop again (it is idempotent).
|
||||
|
||||
@@ -289,12 +289,12 @@ fi
|
||||
# registry lock. Any answer but "free" (0) keeps everything.
|
||||
if [ "$PURGE" = "1" ] && [ -n "$PURGE_UUID" ]; then
|
||||
inuse_rc=0
|
||||
"$(_delegate_py_bin)" -m lib_py.agents in-use "$AGENT" "$PURGE_UUID" "$TARGET_CWD" --wait 5 >/dev/null || inuse_rc=$?
|
||||
inuse_hints="$("$(_delegate_py_bin)" -m lib_py.agents in-use "$AGENT" "$PURGE_UUID" "$TARGET_CWD" --wait 5 2>&1 >/dev/null)" || inuse_rc=$?
|
||||
if [ "$inuse_rc" != "0" ]; then
|
||||
echo "ERROR: conversation $PURGE_UUID is still held by a live agent process (rc=$inuse_rc)." >&2
|
||||
echo " Refusing purge — records preserved (no state was modified)." >&2
|
||||
echo " For claude, see the session files naming it under ~/.claude/sessions/." >&2
|
||||
delegate_publish_event "$DELEGATE_JOB_ID" cancelled "purge aborted: conversation still in use; no state was modified" "$DELEGATE_JOB_TOKEN" "$DELEGATE_JOB_ATTEMPT"
|
||||
echo " Refusing purge — records preserved: the registry row and the conversation files are untouched (the session itself may already be down)." >&2
|
||||
[ -n "$inuse_hints" ] && printf '%s\n' "$inuse_hints" >&2
|
||||
delegate_publish_event "$DELEGATE_JOB_ID" cancelled "purge aborted: conversation still in use; records preserved" "$DELEGATE_JOB_TOKEN" "$DELEGATE_JOB_ATTEMPT"
|
||||
exit 4
|
||||
fi
|
||||
fi
|
||||
|
||||
Reference in new issue
Block a user