time.mktime read ps's local lstart, which is ambiguous in the hour a DST
change repeats: the start could come out an hour off and a live holder be
taken for a reused pid (or the reverse). ps now runs with TZ=UTC0, which BSD
ps and procps both honour, and calendar.timegm reads it. No /proc.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
float("NaN") and a JSON NaN passed the old check, and every comparison with
NaN is false, so a live holder looked like a reused pid: the in-use gate let
the purge through and the session file was taken as stale. startedAt must
now be an int or float (not bool), finite and positive; anything else cannot
be told and counts as live. A dead pid is still stale whatever it says.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A tmp-root entry <key> linking outside made `<key>/<uuid>` look like a plain
directory, and rmtree removed what lay behind the link; projects/<key>,
file-history/ and the other config dirs had no such check either. Each
candidate now keeps the root it was found under (config, projects or tmp),
and is purged only when every component between that root and the leaf is a
real directory and its parent resolves inside the resolved root. A root may
itself be a link (/tmp -> /private/tmp), a leaf link is still removed as the
link alone, and a candidate behind a link is reported as residual instead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
projects/<key>/memory/ (Claude Code's auto memory) sits beside the session
dirs, and `purge claude memory` removed it whole. Only an 8-4-4-4-12 hex id
now reaches the session's directories, session files, jobs and tmp dirs. Any
other id keeps the pre-#31 reach, the `<id>.jsonl` file alone (test_a4 still
purges "uuid-c"); its residual is that file and nothing holds it in use.
The shared _plausible_id is unchanged for the other adapters.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The adapter's fixed /tmp fallback is now a module constant. The new tests
point it, TMPDIR, TMP and TEMP at the test's own dirs, in the test process
and (through a sitecustomize on PYTHONPATH) in every python a test starts,
so no test lists the real /tmp/claude-<uid> any more (Mac review, PR #59).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
stop resolves the uuid before it purges, through lib.sh's exported
CLAUDE_PROJECT_DIR and DiscoveryContext.claude_dir, both of which defaulted
to $HOME/.claude/projects. With CLAUDE_CONFIG_DIR alone the transcript under
<cfg>/projects was not found, so stop removed the row and purged nothing.
Both defaults are now ${CLAUDE_CONFIG_DIR:-<home>/.claude}/projects; an
explicit CLAUDE_PROJECT_DIR still wins.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A session file is what ties a pid's key file to the session, and a job's
state.json what ties the job dir: removing them first left the rest
unattributable after a partial failure (residual: none, and a retry could
not find it). Keys now go before their session file, which stays while any
key does; a job dir is emptied with state.json last and kept if anything
stays. residual lists a session file's key files (masked). A startedAt of 0
or less cannot tell a reused pid and counts as live; the sessions/ listdir
no longer raises.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CLAUDE_CONFIG_DIR moves claude's whole config root, sessions/ included, so a
live session started under it was not seen and a stopped one left
file-history and the rest behind. The config root is now CLAUDE_CONFIG_DIR,
else <home>/.claude; the purge and residual scans look at ctx.claude_dir and
the root's own projects/, each once. The test guard drops the variable for
every test like the other agent-home variables (test_issue55 l1 requires
every *_DIR the adapters read to be accounted for).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude had no residual_artifacts, so a purge always printed `residual: none`.
It now reports names holding the uuid under ~/.claude (projects excluded),
in projects two levels deep, in the tmp roots, plus the owned jobs dir and
session files. Shared files (history.jsonl, ~/.claude.json) are not
reported: they always mention every session. scan_names goes to base.py.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The claude purge removed only <uuid>.jsonl. It now removes everything the
session alone owns: projects/<key>/<uuid>/, file-history/<uuid>/,
session-env/<uuid>/, jobs/<uuid[:8]>/ when its state.json names the session,
a stale sessions/<pid>.json with that pid's key file (name only, never read)
and <tmp>/claude-<uid>/*/<uuid>/. An id that is empty, all dots or holds
/, .. or * builds no path. has_artifacts finds leftovers without the transcript,
and conversation_in_use also sees a live claude through sessions/<pid>.json
(a `claude -c` names no uuid on its command line); a reused pid is told
apart by ps lstart against the file's startedAt.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
handle_startup_dialogs was a claude-only string ladder called only when
AGENT=claude, and wait_for_tui_ready does not treat a dialog screen as ready.
So agy, grok and codex sat on their directory-trust prompt for the full
readiness timeout, the session was recorded as tui-ready-timeout, and
onboarding injection was disabled (create_session.sh:313-314) - a human had to
accept each dialog by hand.
The signatures and accept keys move to the adapters as `startup_dialogs`
(pattern, keys, kind), published through the existing `facts` eval contract as
MAM_STARTUP_DIALOGS. claude's ladder moves over verbatim; agy takes Enter, grok
y, codex Enter, and codex's update notice takes 2 (Skip), which blocks a start
just as hard while changing nothing. kind is `trust` or `notice`:
--no-auto-trust leaves trust dialogs to a human and still answers notices,
since trusting a directory makes codex load its project-local config, hooks and
exec policy.
Re-sending is gated by time (MAM_DIALOG_RESEND_S, default 5s), not by frames.
#24's comment reports that `--source visible` keeps showing an accepted dialog
for a while, and _pane_capture hardcodes that source, so "still on screen" is
not evidence the keys were missed; acceptance is judged by the ready tokens
alone.
Two existing tests asserted the old shape and are updated, not weakened:
b19's "Escape branch stays" now looks for it in claude's adapter, and c1b's
"Enter once per iteration" becomes "never twice for one frame", which the
cooldown satisfies strictly more tightly.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Separate TUI dialog detection into blocking gate layer (_MAM_MODAL_TOKENS) and recovery layer (_MAM_HINT_TOKENS) to eliminate idle dialog starvation
- Add modal_tokens contract across agent adapters (claude, cline) with dynamic scope facts resolution in send_keys_safe
- Implement fail-closed resolution for ambiguous same-kind panes (R-1) and enforce WORKSPACE_ROOT export (R-2)
- Preserve session on TUI readiness timeout when PID is alive for diagnostics
- Add comprehensive test suite in tests/test_c1_tui_readiness.py and adapter contract assertions in test_a4
- Option B / C-3b: Deprecate isolation.root and remove its 4 legacy consumers in lib.sh, workspace_uuid.py, verify_session.py, and stop_session.sh.
- M2~M7 Migration: Implement artifact_path, verify_artifact, purge_artifacts, spawn_spec, resume_spec, auth_ok, discover, ready_tokens, exit_key, and delegate_agent_key across BaseAgentAdapter and all 4 adapters (Claude, Agy, Hermes, Cline).
- Migrate shell duplications in lib.sh, create_session.sh, resume_session.sh, stop_session.sh, and reconcile.sh to python -m lib_py.agents facts.
- Hardening & Corrections: Fix Cline resume_spec flag to '-i --id', apply shlex.quote across all facts variables with MAM_ prefix, add safe fallback in wait_for_tui_ready.
- Add comprehensive contract tests in tests/test_a4_adapter_contract.py and sync IMPROVEMENTS.md / LOG.md.
- Verified: 100% PASS across unit, component, contract, and integration tests.