* fix(runner,hub): reap detached driver CLI on archive and tracking loss
Archive always verifies via stopRunnerSession; runner stopSession scans
argv orphans and awaits webhook-timeout tree-kill so PPID=1 children
cannot accumulate after KillSession miss or map drop.
Fixes#1910
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): keep --hapi-session-id reap-only; test orphan kill path
Claude/cursor swallow the stamp without bootstrap reuse, and
reapRunnerSpawnedOrphans is unit-tested as the stopSession map-miss path.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli,hub): address #1911 CI and bot Majors
Use Vitest in orphanReap tests, add Pi session.on mock, refuse archive
when machine StopSession is unreachable, trust full tree-kill results,
and argv-sweep orphans after every mapped stop.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(runner): gate late-webhook kill on timeout orphans; fail closed on scan error
Track webhook-timeout PIDs separately so recovered shared roots are not
killed, and treat ps-list failures as still_alive rather than empty.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(runner): keep shared Codex wrapper alive when archiving one root
StopSession must detach a single shared root without tree-killing the
wrapper PID while sibling roots remain, including after KillSession has
already marked the runtime inactive.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(runner): keep shared Codex siblings after restart tracking loss
Before persisted-PID or argv orphan kills, consult the durable runtime
registry for active sibling roots on the same wrapper PID so archiving
one root after a runner restart cannot tree-kill live shared sessions.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hub,cli): deliver hub-authored archive metadata to reconnecting CLIs
When KillSession misses and archive is written via markSessionArchivedFromHub,
emit Socket.IO update-session to the session room. On reconnect, ApiSessionClient
reconciles GET /cli/sessions/:id metadata so hub-archived still fires.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(runner): argv-scan orphans even when shared siblings remain
Remove session-wide early returns that skipped orphan reaping whenever any
shared wrapper had active siblings. Keep only the PID-specific filter so
older untracked CLIs with the same HAPI id are still reaped.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(runner): protect tracked shared wrappers when runtime registry is empty
Argv orphan sweeps must exclude in-memory shared wrapper PIDs that still
host sibling roots, not only durable registry siblings — otherwise a missing
runtime record lets the sweep tree-kill the wrapper while archiving one root.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(runner): adopt untracked nonshared CLIs after runner restart
When a runner-spawned Claude webhook arrives with no TrackedSession (typical
after restart mid-bootstrap), durably adopt the PID instead of ignoring it so
StopSession can reap. Keep killing only nonshared timeout orphans; never kill
shared Codex wrappers on late webhooks.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(runner): keep recovered shared wrappers for registry siblings
After restart adoption tracks only a newly reported Codex root, archiving
it must consult the durable registry before tree-killing the wrapper PID
so older active sibling roots survive.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hub): do not archive on KillSession ack when StopSession is unknown
KillSession returns before cleanupAndExit finishes. When the runner cannot
find the PID (unknown), wait for the session KillSession handler to drop
before archiving; refuse if the CLI socket remains reachable.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hub,cli): confirm KillSession pid via StopSession before archive
KillSession now returns the CLI pid. When session-id StopSession is
unknown, archive confirms that OS pid (without treating socket loss as
exit) and still refuses when confirmation stays unknown or still_alive.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(runner,hub): tree-kill adopted CLIs and require PID start markers
Adopted post-restart runner sessions now use killProcessTreeByPid when no
ChildProcess handle exists. KillSession carries a process-start marker so
raw PID confirmation cannot tree-kill a reused OS pid.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(runner,hub): verify adopted PID markers and hub-archive after stop
Adopted sessions compare the persisted start marker before tree-kill and
drop stale tracking on mismatch. After confirmed StopSession, hub-authors
archive metadata when the CLI did not finish its flush.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(runner): refuse unconfirmed shared/adopted stops
Keep shared wrappers when siblings remain, but return unknown unless
runtimeControl already ended the root. Adopted PIDs without a start
marker no longer tree-kill by tracked PID alone.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(runner): require inactive binding before sibling-kept archive
Siblings alone must not flip StopSession to stopped on retry, while an
acknowledged inactive Codex registry binding remains stop proof. Handle
PID- markers before any tracked kill so reuse cannot nuke strangers.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(runner): distinguish orphan reap from unknown keep-wrapper base
Pass unknown into finishWithOrphanSweep so a successful argv reap stays
stopped instead of being overwritten by an absent registry binding.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(runner): honor orphan reap when adopted PID lacks a start marker
finishWithOrphanSweep with an unknown base lets a successful argv kill
report stopped instead of forcing unknown after the marker probe fails.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hub): confirm archive via metadata.hostPid tombstone
When StopSession(sessionId) is unknown and KillSession supplies no
confirmable pid+marker, probe PID-<metadata.hostPid>. already_gone
allows archive; still_alive/unknown still refuse (#1911 Peer #1820).
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): escalate Windows taskkill to /F like SIGTERM→SIGKILL
Soft taskkill /T is refused on win32 console trees; orphanReap left
orphans still_alive. waitForProcessToDie now force-escalates on both
platforms so cross-platform reaping matches.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): widen process.kill mock signal to string | number
Match @types/node process.kill(pid, signal?: string | number) so the
Windows taskkill escalation mocks typecheck on tip 54f141368.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): tasklist liveness + immediate /F after soft taskkill refuse
Windows orphan reap still failed in 5ms on Teemo: soft taskkill refused
but escalate waited on an unreliable process.kill(0) probe. Prefer
tasklist for win32 liveness and escalate to /F as soon as the PID is
still live after soft kill.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): coerce win32 ps-list PIDs and escalate soft taskkill to /F
String PIDs from ps-list made Number.isFinite fail so orphan kill
returned still_alive without calling taskkill. Coerce PIDs, prefer
tasklist liveness, and escalate soft taskkill to /F like SIGTERM→SIGKILL.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): Windows orphan scan via CIM CommandLine + taskkill /F escalate
ps-list/fastlist is missing CommandLine and often missing from single-exe
bundles, so Teemo orphan reap returned scan_failed (logged as live PIDs).
List via Win32_Process CIM, coerce PIDs, escalate soft taskkill to /F.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): grace-wait after successful soft taskkill before /F
Escalate immediately only when soft taskkill is refused. When soft
succeeds but the PID is still draining, honor the existing grace period
before force (Codex Major on #1911).
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(runner): re-verify start marker before orphan argv tree-kill
Capture getProcessStartMarker after the argv match list, re-check
immediately before killTree, and skip on mismatch/null-while-alive so
PID reuse cannot tree-kill an unrelated process on the orphan fallback.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hub): preallocate HAPI id before fresh machine spawn
Fresh POST /machines/:id/spawn omitted existingSessionId, so argv never
got a reap stamp. Preallocate the hub row (fork/clear pattern) and pass
that id so a runner restart before the first webhook remains reapeable.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli,hub): close three post-prealloc unreapable paths
Pass --hapi-session-id into Claude/cursor/kimi/copilot bootstrap so the
preallocated hub row is used. Treat null marker re-check while alive as
still_alive (not stopped). Keep the preallocated stub when spawn-fail
StopSession cannot confirm the child is gone.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): adopt-stub via reservedSessionId, not existingSessionId
`--hapi-session-id` binds a fresh create bootstrap to a hub-preallocated
UUID (getOrCreateSession id) without taking the reopen path. Non-UUID
stamps stay reap-only to match hub create schema. Restores integration
spawn invariants broken by mapping hapi→existingSessionId.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli,hub): close Opus cold-read B1–B3 for #1911
B1: capture start markers from the same process snapshot as argv
match (POSIX ps lstart / Win32 CreationDate); re-check before kill.
Continue other orphans when one target is unconfirmed.
B2: Windows killProcessTreeByPid verifies every collected descendant,
not root-only — taskkill /T exit 0 is signalled, not gone.
B3: runner sets childStarted:false on pre-exec rejection; hub deletes
the preallocated stub without StopSession. Post-exec ambiguity still
keeps the row until stop confirms. Terminal archives no longer refuse
when no runner is connected.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli,hub): explicit adopt for hub-preallocated session stubs
Fresh machine spawn preallocates with tag machine-spawn:<uuid>; CLI
create with a different tag and the same id 409'd on identity conflict.
Add adoptPreallocatedSession (hub-internal stub check) and POST
/cli/sessions { adopt: true, id } so create binds the reserved row and
overwrites tag/metadata without guessing the hub tag. Tests fail-first
on the real prealloc→CLI create path.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): preserve snapshot markers + unify Windows CIM start markers
Runner stopSession was still on findOrphans (PID-only), discarding same-snapshot
markers and reopening the PID-reuse kill window. Switch both call sites to
findTargets. Force UTC ISO 'o' CreationDate for list and single-PID probe so
ConvertTo-Json cannot disagree with getProcessStartMarker on WinPS 5.1.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): exercise production orphan sweep wiring in tests
Extract findStopSessionOrphanTargets as the stopSession discovery path both
run.ts sweeps call. Add tests that drive that helper end-to-end (not an
injected findOrphans seam), assert run.ts source wiring, and cover CIM-list
ISO vs probe ISO agreement plus the broken WinPS shape mismatch.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hub,cli): never delete live spawn on RPC timeout; release stub tags
Ambiguous machine-spawn failures (timeout / unset childStarted) no longer
call stopRunnerSession or deleteSession — that path killed healthy late
boots and CASCADE-wiped transcripts. Pre-exec delete still requires a real
machine-spawn stub (store tag check). Metadata updates release the stub
tag so reopen flavors are not permanently adoptable; adopt refuses
archived stubs. POSIX pgrep ENOENT fails closed instead of root-only verify.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): parseable Windows tree scan + fail closed on CIM failure
Space-joined PowerShell for collectWindowsProcessTree was a WinPS parse
error ($seen=@{} $bfs=@()), so every scan fell open to [root] and B2
false-stopped again. Join with newlines; return scan_failed on error/empty;
killProcessTreeByPid refuses root-only verify. Assert on generated command
text (Teemo-confirmed) so mocks cannot hide the next malformed script.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli,hub): reopen stamps existing-session-id; scan-fail signals root
Opus Critical on #1911: runner always stamps --existing-session-id for
known hub ids so Claude/Kimi/Copilot reopen does not hit adopt-stub 409.
On pgrep/CIM tree-scan failure still signal the verified root but never
claim stopped. Archive when both KillSession and machine RPC are missing.
Empty Win process-list stdout and strict Codex registry reads fail closed.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli,hub): OK: sentinel for Windows tree scan; adopt CAS
CIM failure under SilentlyContinue printed root-only with exit 0 —
byte-identical to a healthy childless tree, so scan_failed never fired
and killProcessTreeByPid false-stopped (#1911 Overseer B1). Require
ErrorActionPreference=Stop, trap exit 1, and an OK: success sentinel;
parse anything else as scan_failed. Same Stop/trap on the machine-wide
list command. Execute the generated scripts under Docker pwsh in CI
when the image is present. Treat pgrep status=null as scan_failed;
coarsen Windows tasklist poll; wrap adoptPreallocatedSession in a
transaction with stub-tag CAS.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli,hub): reservedSessionId for fresh prealloc; signal Win survivors
Fresh machine-spawn stubs must not stamp --existing-session-id — that
sends Codex down reopen without a thread binding (#1911 bot Major) and
re-broke local HTTP non-UUID hints as reopen (round-4). Hub passes
prealloc as reservedSessionId; buildCliArgs stamps --hapi-session-id
for reserved/sessionId and --existing-session-id only for live reopen.
Codex parses --hapi-session-id and adopts via bootstrapSession.
Windows tree kill now taskkills surviving pre-scan PIDs after /T misses
broken intermediate links.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli,hub): fail-loud Docker pwsh CI; scope Codex registry; stub leak
CI skipped all four windowsProcessTree.pwsh tests on 48bb (image inspect
without pull). Pull the image in test.yml and throw under CI when missing.
Scope strict runtime-registry failure to Codex stop contexts so one bad
JSON cannot block archive machine-wide. Stamp childStarted:false on
machine RPC outside_workspace_roots. Add Codex reservedSessionId prealloc
and runSharedCodex regression coverage for the #1911 Critical.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli,hub): single-use reservedSessionId; skip UUID HTTP hints
Consume the machine-spawn reservation on first prepare() so a second
Codex root/fork cannot re-adopt a consumed stub (409). Stop stamping
UUID local-HTTP sessionId as --hapi-session-id (adopt hard-fail). Update
stale hub spawn mocks for the reservedSessionId arity.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hub): archive never-started machine-spawn stubs on unknown
Ambiguous keep-stubs (startedBy=runner, no hostPid, still machine-spawn
tagged) made StopSession return unknown forever while the runner was
online, so archive threw permanently. Treat unknown + prealloc stub +
no hostPid as already_gone. Also reject spawn success when the runner
reports a different id than the preallocated stub.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hub,cli): refuse dual-RPC-miss archive; generation-check Win survivors
Both KillSession and StopSession missing is not proof a detached CLI exited —
keep the row unconfirmed until the runner reconnects. Windows tree kill now
re-checks process-start markers before individually signalling survivors so
PID reuse is not killed.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): refuse archived reopen in bootstrapExistingSession
Hub-archived rows could be resurrected when a late-booting child hit
--existing-session-id: buildSessionMetadata stamped lifecycleState=running
and updateMetadata overwrote the archive. Adopt already rejected archived
stubs; reopen now matches. Intentional revive still goes through hub
reopenSession first. Correct stub-hatch comment (no post-archive argv sweep).
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hub,cli): reject un-archive in updateSessionMetadata CAS
Hub-side transactional guard (Discovery #1911 M1): refuse metadata writes
that clear archived lifecycle unless allowUnarchive (reopen clear). Client
stops infinite updateMetadata backoff when mismatch applies archived.
Clear archive immediately before spawn so Pi/PTY attempt snapshots stay valid.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hub,cli): success+preserve hub archive; M3 reservedSessionId split
Replace version-mismatch refuse (livelock in CAS backoff) with success +
merge-preserve of hub-archived fields unless allowUnarchive. Ack path
routes through noteHubArchived so the CLI exits (criterion 6). Clear-
before-spawn remains the authorized revive. Thread --hapi-session-id to
reservedSessionId for shared-parser flavors (adopt≠reopen).
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): fail-closed orphan ps signal; PID- registry; spawn timeouts
Overseer B1: signalled ps (status null) is scan_failed, not empty table.
B2: unreadable registry on PID-* stops returns null (no soft []).
B3: 10s timeout on process/orphanReap spawn.sync. Narrow bootstrap
archived belt to archivedBy=hub to match hub merge-preserve.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli,hub): hubArchived latch; PTY archiveSnapshot durability
Criterion 6: noteHubArchived latches hubArchived so late
registerKillSessionHandler registrations still EXIT (EventEmitter
does not replay). Mirror Pi: persist archiveSnapshot on
ptyResumeAttempt across clear-before-spawn and quarantine, restore
on failed-resume cleanup (#1911 Discovery Majors).
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): Codex hub-archived exit; AC6 latch test production order
C2: SharedCodexRoot registers hub-archived (and latch check) beside
its private KillSession — Codex never called registerKillSessionHandler.
C1: keep latch+subscribe; test write-then-register order Discovery proved.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): type Codex hub-archived end mock for tsc
vi.fn(async () => {}) inferred zero-arg calls; RootHost['end']
signature unblocks end.mock.calls[0][0] (TS2493).
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): catch hub-archived Codex host.end rejection
void this.host.end() discarded archive-RPC failures as unhandled
rejections; Codex has no unhandledRejection handler, so Node can
exit the shared wrapper and kill sibling roots mid-turn.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Add viewport-driven paging with layout acknowledgements, bounded retries, cancellation gates, and epoch-safe history retention.
Preserve transcript anchors and expansion state, fix tool-group identity collisions, and serialize Android history coordination on Main.
Reduce per-scroll composition and layout work; add native regression tests, CI coverage, and profiling guidance.
#1667 pinned @v1.12 and moved the flaky-relay stream retry tuning from
codex-args into a seeded codex-home config.toml, but the action appends
its own [model_providers.codex-action-responses-proxy] table instead of
merging, so every Codex workflow fails with a TOML duplicate key error.
The overrides cannot be expressed on v1.12 at all.
Pin all three workflows to v1.11, the last release that accepts these
deliberate codex-args overrides under drop-sudo, restoring the original
behavior.
The floating @v1 tag picked up v1.12, whose protected-run validation
rejects model_providers.* overrides passed through codex-args under
drop-sudo, breaking all three Codex workflows. Pin the action to v1.12
and seed a trusted codex-home config.toml with the same settings in an
earlier step instead - the action preserves existing config content.
* fix(web): restore Ctrl+A select-all on the chat page
Chromium's SelectAll collapses to an empty caret when the page contains
a contenteditable (the rich composer) but focus is outside it, so
Ctrl+A + Ctrl+C on the Happy page copied nothing. Take over Ctrl/Cmd+A
at window scope when focus is outside the composer/inputs and select
the message thread manually.
Also removes a duplicate property in markdown-a.test.tsx that broke
`bun typecheck` on upstream/main.
* fix(web): restrict select-all takeover to unshifted Ctrl/Cmd+A; wire e2e spec into CI
Address review findings:
- leave Ctrl+Shift+A to the browser (matches native Chromium, where
the shift variant is unbound)
- run the composer-copy Chromium regression spec in CI alongside
terminal-wrap-fidelity.spec.ts
- move the spec to the root e2e/ dir so the root playwright config
picks it up
* fix(test): stop runner integration suite from leaking detached process trees (#1515)
The default CLI test run included runner.integration.test.ts, which spawns
real detached runner/session process trees. A failing, timed-out, or
interrupted test (or a plain runner stop) left those trees alive under
PID 1 — on the Mac this accumulated ~600 Node/Bun/agent processes and
several GiB of RSS over repeated runs.
Test harness changes only; production runner session-preservation
semantics are untouched:
- Exclude runner.integration.test.ts from the default parallel unit-test
suite; move it into a dedicated serial integration project
(vitest.integration.config.ts, 'bun run test:integration'). The
20-session stress test is opt-in via HAPI_RUN_STRESS_TESTS=true.
- Add a test-owned process/session registry (processRegistry.ts): every
runner, runner-spawned session, and terminal-style child is registered
immediately after spawn; afterEach/afterAll run two-stage cleanup
(logical stopRunnerSession first, then bounded process-tree kill),
followed by a marker sweep for agent grandchildren reparented to PID 1.
- Add a per-run HAPI_TEST_MARKER env stamp + identity/secret env
neutralization for test children (integrationEnv.ts) so outer HAPI/pi
session variables never leak into test processes and the final audit
can recognize test-owned processes by env alone.
- Final suite audit in globalSetup teardown: reap anything still
carrying the run marker and fail with PID/command diagnostics if
anything cannot be reaped, before removing the temp home.
- Regression coverage: a deliberately failing test registers a detached
child and the follow-up audit must find zero test-owned processes.
- CI: replace the dead .env.integration-test step with a dedicated
integration job running the serial project.
* refactor(test): drop unused killByChildProcess import and child field from registry
* chore(test): raise integration hookTimeout to 60s for slow teardown hosts
* fix(test): fail loudly when the process-table audit cannot scan; assert regression child death
Bot review #1521 findings:
- A failed `ps` scan (unsupported flags, buffer exhaustion, permissions)
previously returned [] and silently disabled both teardown audit layers.
It now throws; globalSetup teardown catches the scan error into the
audit error (temp home is still removed) so the run fails visibly.
- The regression audit test cleaned the leak with the reaper before
asserting, and force-killed the fresh marked runner. The failing
test's direct child PID is now asserted dead in afterEach right after
registry cleanup (before the marker sweep), and the audit test stops
its own runner gracefully before reaping.
* fix(test): bound the logical cleanup phase so a hung runner cannot stall the hook
Bot review #1521: stopRunnerSession carries the worker's 60s HTTP timeout
(setup.ts raises HAPI_RUNNER_HTTP_TIMEOUT for the stress test), and the
integration hook timeout is also 60s — N sequential stops could exhaust
the hook budget before the process-tree fallback and marker sweep ran,
recreating the very leak this change prevents.
Logical shutdown is now parallel (Promise.allSettled over all tracked
sessions) and the whole phase (stops + PID resolution) races against a
15s budget, so stage-2 tree-kill and the marker sweep always get their
share of the hook window.
* fix(test): bound graceful runner stop in hooks; keep credentials out of audit diagnostics
Bot review #1521 (follow-up):
- stopRunner()'s HTTP stop can burn the worker-wide 60s timeout on a
hung-but-live runner, starving the marker sweep within the hook budget.
afterEach/afterAll now race the graceful stop against a 10s bound; a
runner that does not stop in time is force-reaped by the sweep (it
carries the run marker) and the next beforeEach's alive-PID guard
ignores any stale state file.
- The env-bearing ps scan (ps eww) was also used for diagnostics, so the
first 500 chars of a short-command process could print inherited
credentials (CLI_API_TOKEN etc.) into teardown error logs. The scan now
only identifies marked PIDs; command lines are fetched separately
without 'e', falling back to '(command unavailable)' instead of the
env dump.
* fix(test): reap runner model-probe orphans before the zero-survivor inspection
Bot review #1521 (Minor): inspect-before-reap. Applying it exposed a real
race: each test's runner legitimately spawns marker-carrying children at
startup (agent acp + agent --list-models model-catalog probes). Stopping
the runner orphans them (ppid 1) with the run marker, so the audit test's
OWN runner polluted the pure inspection with fresh probes spawned after
the failing test's sweep window.
- reapTestOwnedProcesses now re-kills every re-scan iteration instead of
killing once and only re-scanning, so a process that survived its first
SIGKILL (mid-exec) or spawned mid-kill is not given a free pass.
- The regression audit test stops its runner, reaps (clearing its own
legitimate orphan probes), then inspects: anything still marked is a
genuine survivor the bounded reaper could not remove and fails the
suite. Killable leaks from the failing test are already asserted dead
in afterEach before the sweep runs.
* fix(test): strictly bound the marker reaper; make per-test sweep unconditional and verified
Bot review #1521 (follow-up):
- The 10s reap deadline did not bound the awaited per-tree kills: each
killProcessTreeByPid can wait up to 2s per PID, so several stuck
processes could still exceed the 60s hook budget. Every process in a
test-owned tree carries the marker (env is inherited), so tree-walking
is unnecessary: the reaper now SIGKILLs every marked PID found by each
scan, fire-and-forget, and re-scans every 250ms — the deadline strictly
bounds the function.
- The per-test sweep was skipped when the direct-child assertion failed
first, and its survivors were ignored. afterEach now snapshots the
regression-child state BEFORE the unconditional sweep, then verifies
both the registry result and the sweep leftovers.
* fix(test): replace it.fails regression with a direct assertion test
Bot review #1521 (Minor): Vitest applies the it.fails expected-failure
inversion after afterEach, so a broken registry assertion inside the hook
would be masked as an expected failure, and the marker sweep would erase
the evidence before the follow-up audit ran.
The regression is now a normal test that registers a detached child at
spawn time, deliberately performs NO per-test teardown, runs only the
spawn-time registered cleanup, and asserts the child PID is dead. The
afterEach no longer carries the registry-leak assertion (moved into the
test body where it cannot be inverted); the per-test sweep assertion and
the final audit test are unchanged.
* fix(test): bound registry stage-2 tree-kills; require live regression fixture
Bot review #1521 (follow-up):
- Stage-2 killProcessTreeByPid awaits per descendant serially and can
consume the whole 60s hook for a large/stuck tree. Signals are all
delivered synchronously (children first) before any waiting, so racing
the awaits against a 5s budget bounds the phase without skipping any
kill; waitForAllDead still verifies the outcome.
- The regression test could pass vacuously if its fixture exited during
the startup delay (the registry exit listener would remove it before
cleanup). It now asserts the child is alive before running cleanup.
* fix(test): kill registered roots with bare synchronous SIGKILL, no pgrep walk
Bot review #1521 (follow-up): racing the mapped killProcessTreeByPid
calls against a timer does not bound the phase — evaluating the map
invokes each call immediately, and each runs the recursive synchronous
pgrep walk before its first await, which can consume the hook before the
timer, runner stop, or marker sweep run.
Stage-2 now SIGKILLs registered roots directly (fire-and-forget, no
tree walk, no per-PID waits) and waits a bounded 5s for death.
Descendants are reaped by the unconditional marker sweep immediately
afterward — every descendant inherits the run marker, so tree-walking is
unnecessary.
* fix(test): drop duplicate process-death wait in registry cleanup
Bot review #1521 (Minor): the duplicated waitForAllDead delayed the
authoritative marker sweep by another 5s under the exact stuck-process
condition the harness must handle. Keep the single bounded wait; the
afterEach marker sweep remains the guarantee.
Adds GitHub Actions workflow to auto-respond to new issues using OpenAI's Codex action.
Features:
- Triggers on newly opened issues
- Skips issues with duplicate/spam/bot-skip labels or existing bot responses
- Includes comprehensive prompt for context-aware responses
- Enhanced security constraints and response format guidelines
- Checks for existing HAPI Bot responses before processing
Files:
- .github/workflows/issue-auto-response.yml - GitHub Actions workflow
- .github/prompts/issue-auto-response.md - Codex prompt with project context and guidelines
Adds automated deployment to GitHub Pages with custom domain app.hapi.run, and updates vite.config.ts to support dynamic base URL via VITE_BASE_URL environment variable
Consolidate version bumping, building, npm publishing, and git operations into a single release script that handles platform packages first. This solves the issue where optionalDependencies needed platform packages published before bun install could generate complete lockfile hashes.
Changes:
- Created cli/scripts/release-all.ts with support for --dry-run, --publish-npm, and --skip-build flags
- Removed release-it dependency and old release/publish-npm scripts
- Simplified GitHub Actions release workflow to always use --generate-notes
- Deleted obsolete release configuration files (.release-it.json, .release-it.notes.js, publish-npm.ts)
- Update CLI version to 0.1.0
- Change bin script extension from .js to .cjs for ES module compatibility
- Add release-it as dev dependency for version management
- Update all platform-specific binary package versions to 0.1.0
- Enhance release workflow to support custom RELEASE_NOTES.md
- Update CI workflow to package release artifacts with correct naming (hapi-* prefix)
- Generate SHA256 checksums for all artifacts
- Auto-update homebrew-tap repository on release
- Add Installation section to README with Homebrew, npm, and binary options
- Simplify .release-it.json config as CI now handles release process
- Add update-homebrew-formula script to update formula in homebrew-tap
- Add release-artifacts directory to .gitignore
Users can now install via: brew install tiann/tap/hapi