diff --git a/README.md b/README.md index e693135..6bc6357 100644 --- a/README.md +++ b/README.md @@ -343,11 +343,27 @@ There is no supported way back from that. The transcript has to be rolled back to the last message Anthropic issued: ```bash -claude-mode repair-session # list transcripts here and their state +claude-mode repair-session # transcripts for this project +claude-mode repair-session --all # every project, problems only claude-mode repair-session # show what it would cut claude-mode repair-session --apply ``` +Scope, since it is not obvious: a bare listing covers only the project you are +standing in (walking up from the current directory to find it), while a **named +session id is looked up across every project** — you rarely remember which +project a session you cannot resume belonged to. `--all` drops the scoping +entirely. + +`--all` reports only what is actually actionable, which matters more than it +sounds. Of 59 transcripts here it initially flagged 16; on inspection 5 had +simply never received a reply, and 10 had run start-to-finish on a gateway, so +every id in them is that provider's by design. Those resume perfectly well under +the provider they were born on, have nothing to truncate back to, and are only a +problem if you try to resume them as Anthropic. Neither is damage, so neither is +listed. Only a transcript that has a genuine `msg_` message *and* junk after it +is something this can or should touch. + ### The cut turns are not thrown away Truncating is the mechanical fix, but the turns being cut are the work itself — diff --git a/linux/claude-mode b/linux/claude-mode index aec20db..82fc7ad 100755 --- a/linux/claude-mode +++ b/linux/claude-mode @@ -2122,18 +2122,52 @@ cm_project_dir() { } cmd_repair_session() { - local target='' apply=0 reinject=1 dir='' a file verdict age + local target='' apply=0 reinject=1 scan_all=0 dir='' a file verdict age for a in "$@"; do case "$a" in --apply) apply=1 ;; --dry-run) apply=0 ;; --no-reinject) reinject=0 ;; --list) target='--list' ;; + --all) scan_all=1 ;; -*) err "unknown option '$a'"; return 1 ;; *) target="$a" ;; esac done + # A session you need to repair is one you could not resume, which is a poor + # position from which to remember which project it belonged to. --all drops + # the working-directory scoping and reports only what is actually broken. + if [ "$scan_all" -eq 1 ]; then + head_ 'scanning every session transcript' + local f v bad=0 total=0 + for f in "$CM_SETTINGS_DIR"/projects/*/*.jsonl; do + [ -f "$f" ] || continue + total=$((total + 1)) + v="$("$PY" "$JSON" repair-session "$f" 2>/dev/null)" || continue + printf '%s' "$v" | "$PY" -c " +import json,sys,os +d=json.load(sys.stdin) +# Only 'repairable' is damage this tool can or should act on. +if d['kind'] != 'repairable': raise SystemExit(0) +print(' %-38s %s' % (os.path.basename(d['path'])[:-6], os.path.basename(os.path.dirname(d['path'])))) +print(' would drop %d line(s); %d foreign id(s)' % (d['dropLines'], len(d['foreignIds']))) +raise SystemExit(9) +" && continue + bad=$((bad + 1)) + done + printf '\n' + if [ "$bad" -eq 0 ]; then + ok "nothing to repair across $total transcript(s)" + else + warn "$bad of $total transcript(s) were cut short by a mode switch" + printf ' %sclaude-mode repair-session --apply%s\n' "$C_DIM" "$C_RESET" + fi + printf ' %ssessions that ran entirely on a gateway are not listed: they carry that%s\n' "$C_DIM" "$C_RESET" + printf ' %sprovider'"'"'s ids by design and resume fine under it%s\n' "$C_DIM" "$C_RESET" + return 0 + fi + # Claude Code keys transcripts by the directory the session was started in, # which is rarely the one you are standing in when you come to fix it. Walk # up first, and for a named session fall back to looking through every @@ -2172,6 +2206,7 @@ print(' %-40s %s' % (os.path.basename(d['path'])[:-6], state)) " done printf '\n %sclaude-mode repair-session --apply%s\n' "$C_DIM" "$C_RESET" + printf ' %s--all checks every project, not just this one%s\n' "$C_DIM" "$C_RESET" return 0 fi @@ -2202,9 +2237,15 @@ print() if d['healthy']: print(' %sok %s last message is Anthropic-issued; nothing to repair' % (G,X)) raise SystemExit(0) +if d['kind'] == 'gateway-native': + prov = (d['foreignIds'][0]['model'] or 'a gateway') if d['foreignIds'] else 'a gateway' + print(' %sok %s this session ran entirely on %s' % (G,X,prov)) + print(' %sits ids come from that provider by design; it resumes under it, not%s' % (D,X)) + print(' %sunder Anthropic. There is nothing here to repair.%s' % (D,X)) + raise SystemExit(0) if not d['repairable']: - print(' %sFAIL%s no Anthropic-issued message anywhere in this transcript' % (R,X)) - raise SystemExit(1) + print(' %sok %s this transcript has no assistant replies to resume from' % (G,X)) + raise SystemExit(0) for f in d['foreignIds']: print(' %swarn%s line %d carries a %s id from %s' % (Y,X,f['line'],f['id'].split('-')[0]+'-',f['model'] or 'another provider')) n = sum(1 for s in d['syntheticIds'] if s['apiError'] and s['line'] > d['lastGoodLine']) diff --git a/linux/cm-json.py b/linux/cm-json.py index 62c9aca..fc6acd0 100644 --- a/linux/cm-json.py +++ b/linux/cm-json.py @@ -750,6 +750,23 @@ def cmd_repair_session(argv): if lines[i].strip() and _msg_id(_safe(lines[i])) is not None] healthy = (last_good >= 0 and not tail_ids) + # Not every transcript without an Anthropic id is damaged, and saying so + # turns the scan into noise. A session that ran start to finish on a gateway + # has `gen-` ids throughout by design: it resumes perfectly well under the + # provider it was born on, has nothing to truncate back to, and is only a + # problem if you try to resume it as Anthropic. Likewise a session that never + # got a reply at all has no ids and nothing wrong with it. + if healthy: + kind = "healthy" + elif last_good >= 0: + kind = "repairable" + elif foreign: + kind = "gateway-native" + elif synthetic: + kind = "synthetic-only" + else: + kind = "no-messages" + out = { "path": path, "lines": len(lines), @@ -758,6 +775,7 @@ def cmd_repair_session(argv): "foreignIds": foreign, "syntheticIds": synthetic, "healthy": healthy, + "kind": kind, "repairable": (not healthy) and last_good >= 0, "applied": False, "backup": "",