Repository navigation
fix(server): archiving a thread stops only its own agent-browser daemon, over its socket - #56
Conversation
…on, over its socket Archiving or deleting a thread stopped its agent-browser daemon with SIGTERM to the process its .pid named, after checking that the command line was agent-browser's. A stale .pid whose number another session's daemon has now would stop that daemon and could release its tab, and the check and the signal raced (found in LazurioPlatform#348, which had the same path). stopSessionDaemon now sends agent-browser's own graceful shutdown (__agent_browser_internal_shutdown, as its CLI does before restarting a daemon) on the session's own socket, t3-<thread>.sock, and waits up to 5 s for the .pid to go. Nothing listening means no daemon; a refusal or no answer is "failed" and is logged. No process id is signalled. The tests run a live process named agent-browser whose id a stale t3-a.pid names, with only t3-b's daemon listening: archiving touches neither. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ct runtime The socket shutdown waits for the daemon's answer and for its .pid to go with setTimeout and Date.now, as the Platform's copy does. The fork's typecheck flags both (globalTimers, globalDate) unless the file opts out; holders.ts now does, like the other overlay files that run outside the Effect runtime. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s gone As in LazurioPlatform#348 after Pablo's review: an acknowledged shutdown whose .pid is still there after the wait, or a lost answer after the command went out, is "unconfirmed" (the daemon may still hold the tab), and a daemon that answers it did not shut down is "refused". Both are logged as thread-tabs-daemon-stop-failed with the outcome; only a gone .pid is "stopped". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
agentrozjedemeai
left a comment
There was a problem hiding this comment.
@immakermatty Exact-head review of be5e8e5 (base b122de4). The stale-PID cross-session regression, archive/delete/startup call paths, t3-only filter, and runbook look aligned; CI is green and review threads are clear. Two error-path regressions violate the stated outcomes:
-
apps/server/src/lazurio/holders.ts:421-425,457-464: fileExists turns every fs.access rejection into false. In an isolated Node 26 fixture, I wrote t3-a.pid, had ask() return success while changing its parent directory mode to 000, and stopSessionDaemon returned "stopped" while t3-a.pid was still present (fs.access rejected EACCES; restoring the directory proved the file persisted). This violates the completion rule that only a gone .pid counts as stopped. Treat ENOENT as gone; permission/IO errors must be unconfirmed (or throw so the caller logs a failure), not stopped. Add a regression test for inaccessible .pid/parent.
-
apps/server/src/lazurio/holders.ts:417: askDaemonSocket maps every pre-connect error to "absent". An isolated listening Unix server with its t3-a.sock mode set to 000 returned {kind:"absent"} while server.listening was true. EACCES is not "nothing listens"; surface it as unconfirmed/failure rather than "none", and cover that case. Reserve "absent" for ENOENT/ECONNREFUSED (and other proven absence).
The focused suite was not rerun locally: vp/dependencies are absent in this disposable checkout and host memory pressure is high. I verified the two paths with locked, isolated Node fixtures, inspected the diff, and confirmed the exact-head CI run 38264853671 succeeds. Please fix these outcomes on a new head and repeat exact-head checks before merge/release.
…no daemon or stopped Pablo's review of #56: every connection error before connect counted as "nothing listens", so a daemon whose socket cannot be opened (EACCES) was reported as none; and every failure of fs.access counted as "the .pid is gone", so an acknowledged shutdown whose .pid cannot be checked was reported as stopped. Only ENOENT, ECONNREFUSED and ENOTSOCK now prove that nothing listens, and only ENOENT proves the .pid gone; any other error is unconfirmed. Tests cover a listening socket of mode 000 and a .pid in a directory that cannot be read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
agentrozjedemeai
left a comment
There was a problem hiding this comment.
@immakermatty APPROVED on exact head 4988c8f (base/current main b122de4). Re-review of my be5e8e5 blockers: fileExists treats only ENOENT as gone and failed PID checks return unconfirmed; the socket maps only ENOENT/ECONNREFUSED/ENOTSOCK before connect to absent, with permission/other errors unconfirmed. I exercised the exact source in a locked isolated Node 26 fixture: a stale A PID naming a live agent-browser process with only B listening returned none, B heard nothing and the process survived; chmod-000 listening socket and inaccessible-but-existing PID both returned unconfirmed; refusal, no answer, gone PID and rejected traversal behaved as specified. I also traced archive/delete/startup sweep and t3-only selection and checked the runbook. git diff --check clean; current-main merge-tree clean; CI run 38268788945 is green on this SHA (format, server/web typecheck, overlay tests and builds). No unresolved threads or other reviewer requests. Native vp tests were not rerun in this disposable checkout because vp/dependencies are absent; focused source fixture and exact-head CI are the execution evidence. This approves the PR head, not a release or any future head.
…aemon or stopped Pablo's review of Lazurio/t3code#56 found two error paths in the overlay's copy of stopSessionDaemon, and the Platform's (#348, tagged for rc.58) has them too: every connection error before connect counted as "nothing listens", so a daemon whose socket cannot be opened (EACCES) was reported as none; and every failure of access() counted as "the .pid is gone", so an acknowledged shutdown whose .pid cannot be checked was reported as stopped. Only ENOENT, ECONNREFUSED (Linux) and ENOTSOCK (macOS) now prove that nothing listens, and only ENOENT proves the .pid gone; any other error is unconfirmed. The test covers a listening socket of mode 000 and a .pid in a directory that cannot be read; it fails on main's window.ts. F47's addendum names the rule. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… MIT boundary (Pablo's second review)
- A newer /.lazurio/shell.js inside an older app could have announced
majors its app's own parser does not read, since the identity responder
comes from the shell and the parser from the app. The responder now names
only the majors the app hands it, so a newer shell inside an older app
answers without majors and gets v1; the vectors test that combination
against the Launchpad's and T3 Code's current parsers.
- The MIT exception lists its three parts: the module's directory, the
artifact's browserContract files and, by Matěj's grant of 2026-10-12
("MIT i pro kopii"), the functions T3 Code's overlay copied from holds.ts
at 49d54c9. The MIT terms ship as the module's own licence file; the
repository's root LICENSE stays Elastic-2.0 and is never shipped as the
contract's.
- Phase 1c lists #347, #348, #349 and #351 as merged, and phase 2 names
Lazurio/t3code#56 as merged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… MIT boundary (Pablo's second review)
- A newer /.lazurio/shell.js inside an older app could have announced
majors its app's own parser does not read, since the identity responder
comes from the shell and the parser from the app. The responder now names
only the majors the app hands it, so a newer shell inside an older app
answers without majors and gets v1; the vectors test that combination
against the Launchpad's and T3 Code's current parsers.
- The MIT exception lists its three parts: the module's directory, the
artifact's browserContract files and, by Matěj's grant of 2026-10-12
("MIT i pro kopii"), the functions T3 Code's overlay copied from holds.ts
at 49d54c9. The MIT terms ship as the module's own licence file; the
repository's root LICENSE stays Elastic-2.0 and is never shipped as the
contract's.
- Phase 1c lists #347, #348, #349 and #351 as merged, and phase 2 names
Lazurio/t3code#56 as merged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Status: ready for review
Problem
Archiving or deleting a thread stops its agent-browser daemon so the thread's tabs can end (F47 point 5, task 501). The overlay sent SIGTERM to the process the thread's
.pidnamed, after checking that/proc/<pid>/cmdlinewas agent-browser's. A stalet3-<a>.pidwhose number another session's agent-browser daemon has now would stop that daemon and could release its tab. The check and the signal also race. Pablo found the same path in Lazurio/LazurioPlatform#348 (lazurio browser done); this is the overlay's copy.Fix
stopSessionDaemon(inapps/server/src/lazurio/holders.ts) sends agent-browser's own graceful shutdown,{"action":"__agent_browser_internal_shutdown"}, on the session's own socket<socket dir>/t3-<thread>.sock. agent-browser's CLI sends the same inrequest_graceful_daemon_shutdownbefore it restarts a daemon. It then waits up to 5 s for the.pidto go. The daemon of an attached browser disconnects without closing a tab and removes its.pid, so the binding stops counting for the hub. No process id is signalled.stoppedonly once the.pidis gone;nonewhen nothing listens on the socket (no daemon, or a stale socket file);refusedwhen the daemon answers that it did not shut down;unconfirmedwhen no answer came after the command went out or the.pidis still there after 5 s, so the daemon may still hold the tab.threadTabs.tslogsthread-tabs-daemon-stop-failedwith the outcome for the last two, replacingthread-tabs-daemon-not-agent-browser.Not changed
t3-.agent-browser closeis still never called, because it starts a daemon first when none runs.Verification
vp test run src/lazurio/holders.test.ts src/lazurio/threadTabs.test.ts(apps/server): 53 passed./bin/sleep) has the number a stalet3-a.pidnames, and onlyt3-b's daemon listens: stoppingt3-aanswersnone,t3-bhears nothing and the process stays alive.none; refusal and no answer givefailed.t3-a's own daemon gets exactly one shutdown line, and its.pidgoes..pidis untouched.vp lintandvp fmt --checkon the changed files: clean. The CI typecheckvp run --filter t3 typecheck: 0 errors. The first head failed it onglobalTimers/globalDate;holders.tsnow opts out like the other overlay files that run outside the Effect runtime.This should land before the stable
0.0.45-lazurio.8, which would otherwise ship the PID path.Plan: DEV-6646 task 2026-10-12-702 (the Platform side is Lazurio/LazurioPlatform#348).
Claude Opus 5.5 in Claude Code.
🤖 Generated with Claude Code