Summary
A background hackbrowser crawl is aborted at every session turn-end, not just on real cancellation — so it dies after only a handful of pages with most of its page queue and maxPages budget unused. A crawl launched with maxPages=200 was observed stopping at 3 pages (12 queued) purely because the orchestrator finished one response cycle.
Root cause (two abort paths tied to the turn)
SessionPrompt.cancel() unconditionally called stopHackbrowser, and cancel() runs via defer() on every loop/prompt exit (normal turn-end, not only abort). So each turn boundary killed the crawl.
- The hackbrowser tool passed
signal: ctx.abort (the agent TURN's abort) to the launcher, which forwards an abort to the worker when the signal fires — a second turn-coupled kill path.
Proxy-agent subagents were never affected: they run within the turn (parent awaits them) and cascade-cancel only on genuine abort. The crawl was the sole component wrongly bound to turn-end.
Fix (this issue)
cancel(sessionID, { stopCrawl }) — default false. Turn-end defers leave the crawl running; only genuine teardown stops it.
/abort route (Esc) passes stopCrawl: true.
- hackbrowser tool no longer passes
signal: ctx.abort.
Session.remove stops the crawl on session delete.
Result: the crawl runs in the background to its own maxPages or until its queue drains; proxy-agents test captured endpoints across turns; the crawl stops only on Esc / session-delete / /hackbrowser-stop. The agent cannot end the crawl by finishing a turn.
Follow-ups (separate)
Fixed on stage; shipping in the next canary for verification. Leaving open until confirmed.
Summary
A background hackbrowser crawl is aborted at every session turn-end, not just on real cancellation — so it dies after only a handful of pages with most of its page queue and
maxPagesbudget unused. A crawl launched withmaxPages=200was observed stopping at 3 pages (12 queued) purely because the orchestrator finished one response cycle.Root cause (two abort paths tied to the turn)
SessionPrompt.cancel()unconditionally calledstopHackbrowser, andcancel()runs viadefer()on every loop/prompt exit (normal turn-end, not only abort). So each turn boundary killed the crawl.signal: ctx.abort(the agent TURN's abort) to the launcher, which forwards an abort to the worker when the signal fires — a second turn-coupled kill path.Proxy-agent subagents were never affected: they run within the turn (parent awaits them) and cascade-cancel only on genuine abort. The crawl was the sole component wrongly bound to turn-end.
Fix (this issue)
cancel(sessionID, { stopCrawl })— default false. Turn-end defers leave the crawl running; only genuine teardown stops it./abortroute (Esc) passesstopCrawl: true.signal: ctx.abort.Session.removestops the crawl on session delete.Result: the crawl runs in the background to its own
maxPagesor until its queue drains; proxy-agents test captured endpoints across turns; the crawl stops only on Esc / session-delete //hackbrowser-stop. The agent cannot end the crawl by finishing a turn.Follow-ups (separate)
Fixed on
stage; shipping in the next canary for verification. Leaving open until confirmed.