Rename a resumed session's husk with an 'X ' prefix
This commit is contained in:
@@ -29,6 +29,8 @@ DEFAULT_PROMPT = (
|
||||
"transient failure (usage limit, API outage, or network), which has cleared."
|
||||
)
|
||||
|
||||
DEFAULT_PREFIX = "X "
|
||||
|
||||
# Every quota Claude Code can report as "You've hit your <limit> · <how to clear
|
||||
# it>", including the ones that cost money rather than time.
|
||||
QUOTA_BLOCK = re.compile(
|
||||
@@ -287,11 +289,42 @@ def candidates(sessions, jobs_dir, now):
|
||||
"blocked_at": blocked_at,
|
||||
"name": session.get("name"),
|
||||
"session_id": session_id,
|
||||
"state_file": state_file,
|
||||
})
|
||||
return sorted(found, key=lambda job: job["blocked_at"])
|
||||
|
||||
|
||||
def resume(job, claude_bin, prompt, dry_run):
|
||||
def mark_superseded(state_file, prefix):
|
||||
"""Prefix the husk's name so it reads apart from its successor, or say why not.
|
||||
|
||||
`claude agents` takes the name from state.json and offers no command to set
|
||||
it, so the file is the only seam.
|
||||
"""
|
||||
try:
|
||||
state = json.loads(state_file.read_text())
|
||||
except (OSError, json.JSONDecodeError) as err:
|
||||
return str(err)
|
||||
name = state.get("name")
|
||||
if not isinstance(name, str) or name.startswith(prefix):
|
||||
return None
|
||||
|
||||
state["name"] = prefix + name
|
||||
flags = state.get("respawnFlags")
|
||||
if isinstance(flags, list) and "--name" in flags:
|
||||
at = flags.index("--name") + 1
|
||||
if at < len(flags):
|
||||
flags[at] = state["name"]
|
||||
|
||||
staged = state_file.with_name(state_file.name + ".new")
|
||||
try:
|
||||
staged.write_text(json.dumps(state, indent=2))
|
||||
staged.replace(state_file)
|
||||
except OSError as err:
|
||||
return str(err)
|
||||
return None
|
||||
|
||||
|
||||
def resume(job, claude_bin, prompt, prefix, dry_run):
|
||||
cwd = job["cwd"]
|
||||
if not cwd or not Path(cwd).is_dir():
|
||||
log(f"{job['id']}: skipped, working directory is gone ({cwd})")
|
||||
@@ -326,6 +359,10 @@ def resume(job, claude_bin, prompt, dry_run):
|
||||
)
|
||||
except (OSError, subprocess.SubprocessError) as err:
|
||||
log(f"{job['id']}: could not retire stale entry: {err}")
|
||||
|
||||
problem = mark_superseded(job["state_file"], prefix)
|
||||
if problem:
|
||||
log(f"{job['id']}: could not rename stale entry: {problem}")
|
||||
return True
|
||||
|
||||
|
||||
@@ -367,6 +404,12 @@ def main():
|
||||
help="memory a resume needs the host to have spare, in MB (default: 2048)",
|
||||
)
|
||||
parser.add_argument("--prompt", default=os.environ.get("WATCHDOG_PROMPT", DEFAULT_PROMPT))
|
||||
parser.add_argument(
|
||||
"--superseded-prefix",
|
||||
default=os.environ.get("WATCHDOG_SUPERSEDED_PREFIX", DEFAULT_PREFIX),
|
||||
help="prepended to the name of a husk once its successor runs "
|
||||
f"(default: {DEFAULT_PREFIX!r})",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
claude_bin = resolve_claude(args.claude_bin)
|
||||
@@ -417,7 +460,7 @@ def main():
|
||||
if strain:
|
||||
log(f"holding off: {strain}")
|
||||
break
|
||||
if not resume(job, claude_bin, args.prompt, args.dry_run):
|
||||
if not resume(job, claude_bin, args.prompt, args.superseded_prefix, args.dry_run):
|
||||
continue
|
||||
ledger["attempts"][job["session_id"]] = tried + 1
|
||||
ledger["resumes"] = recent + [now.timestamp()]
|
||||
|
||||
Reference in New Issue
Block a user