feat(config): T-1277 — detach, and a failed job that looked busy
core/process.py spawns a child that outlives its parent: its own session, so a signal to the parent's group or a timeout kill does not take the work with it; re-execing reach by BARE NAME, because an absolute path would freeze the child to whichever checkout was current at spawn time and silently run the wrong source after a repoint; and streams kept separate exactly as in the foreground, events to <id>.jsonl and real output to <id>.out. Testing a case the ticket did not name found a real hole. Recording completion inside @command looked right and was wrong: a child that fails BEFORE any command runs — bad arguments, an unknown verb, an import error — never reaches that decorator. `reach --detach check bogus` left its metadata reading "running" forever with the process long gone. That is the exit-0 trap wearing a new disguise and worse than the original, because a failed job that looks busy sits somewhere nobody is watching, and a caller polling for completion would wait indefinitely on something that failed in milliseconds. So completion is recorded at the PROCESS's exit instead. main.py gains main(), wrapping cli() in a single try/finally, and the entry point moves to main:main. Every exit path now passes through one place. Removed from @command rather than left in both — two writers of one field is how they drift. Verified on three paths: success records done/0, a real drift failure records failed/1, and the parse failure that exposed the hole now records failed/2. One narrow conformance exemption, with its reason inline so it does not read as an oversight: the no-domain-imports-core.jobs invariant fired on main.py, correctly by its letter and wrongly by its purpose. main.py is not a command; it is the entry point, and it already owns --detach. Still open, and carried to T-1278: a child killed outright cannot record anything, so jobs list must reconcile against process liveness rather than trusting the file. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -111,11 +111,15 @@ def check_jobs_stay_ambient(failures: list[str]) -> None:
|
||||
`core.jobs`, that has become call-site discipline again, and it will fail the
|
||||
same way: one command forgets and its output loses correlation silently.
|
||||
|
||||
core/ is exempt — command.py and console.py are where the ambience is
|
||||
implemented.
|
||||
Exempt: core/ (command.py and console.py implement the ambience) and
|
||||
main.py. main.py is not a command — it is the entry point, and it already
|
||||
owns the invocation-level concerns --detach, --verbose and --no-input.
|
||||
Recording a detached child's completion at the PROCESS's exit belongs there
|
||||
for the same reason, and is far less coupled than the --detach flag it
|
||||
already carries.
|
||||
"""
|
||||
for path in _package_files():
|
||||
if path.parent.name == "core":
|
||||
if path.parent.name == "core" or path.name == "main.py":
|
||||
continue
|
||||
source = path.read_text(encoding="utf-8")
|
||||
tree = ast.parse(source, filename=str(path))
|
||||
|
||||
Reference in New Issue
Block a user