Files
PaperClipAI/server/src
DottaandPaperclip 1c29992171 Observe workspace service failures before asynchronous startup work (#19624)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Workspace runtime services start local child processes and wait for
readiness.
> - The runtime must return startup failures through its normal error
and cleanup path.
> - A child can fail while asynchronous log or registry work is pending.
> - The previous code attached child listeners too late, or rejected
promises before the readiness race observed them.
> - This pull request records the child failure immediately and delivers
it when the readiness race can observe it.
> - The benefit is a preserved startup error without a process-wide
unhandled rejection.

## Linked Issues or Issue Description

**What happened?**

A real child that exits during a pending registry write produces an
unhandled rejection. A spawn error during a pending log close loses the
original error and reaches the readiness timeout instead. This caused
the General server 8/12 CI shard for #19576 to fail after all 2,285 test
assertions passed.

**Expected behavior**

The startup operation returns the original child failure, removes its
registry record, and produces no global unhandled rejection.

**Steps to reproduce**

Run the new readiness test against the base revision. It starts a real
exit-1 shell while holding the registry write through child close. It
also starts a missing shell while holding log close through the spawn
error. The two behavioral checks fail before the production change.

**Paperclip version or commit**

da9a4cb96c, before this change.

## What Changed

- Register child error and close listeners directly after spawn, before
the first asynchronous boundary.
- Keep the observed failure as a resolved outcome until the readiness
race attaches. The race then rejects through the existing cleanup path.
- Add real-process regressions for both asynchronous windows. Check the
original error, no global unhandled rejection, process exit, and
registry removal.

## Verification

- Behavioral RED before production edits: two actual subprocess cases
failed on the original code. A fast child exit while registry
publication was held emitted an unhandled rejection; an ENOENT spawn
while log close was held lost its original error and reached readiness
timeout.
- Final GREEN at `322ef0d21b3cd1811913bb5e15f47355ad5fa449`: six
affected files, 72 passing tests and three existing platform skips
(149.20s). Coverage includes both real failure windows, original
operation errors, zero global unhandled rejections, child exit and
registry cleanup, ordinary startup/stop, EADDRINUSE classification,
bounded timeout/lease guards, six model fixtures and all 15 Slack owner
cases.
- All seven server typecheck layers, module-boundary and whitespace
checks pass. Required preflight passes in 20.979s. The final diff
contains only the two readiness owner/test files; no new code comments
were added.
- [Final-head
CI](https://github.com/paperclipai/paperclip/actions/runs/42311878548):
56 successful checks, four intentional skips, no failures or pending
checks. Required E2E and verify aggregates pass. Greptile reviewed this
exact head at 5/5 with no unresolved findings.
- The valid review finding about a late test subscription is fixed: the
real spawn wrapper captures settlement before returning the child. The
resolved thread and final test both preserve the original lifecycle
oracle.
- Prior inherited CI failures were repaired through normal merges of
existing fixes: #19628 (duplicate workflow defaults), #19610 (model
expectations), #19627 (deterministic Slack deadline fixture), and #19646
(current Runner lane flags). All four exact `--package-lane=1/3`, `2/3`,
`3/3`, and `--native-server` dry runs and the lane-inventory regression
pass. No CI job was replayed, timeout weakened, or production assertion
changed to accommodate those failures.
- Local environment: macOS arm64, Node 26.4.0, pinned pnpm 9.15.4,
Vitest 5.0.3. Subprocess and loopback checks ran with the required local
permissions. These checks do not claim a hosted deployment or full
browser acceptance.

## Risks

- Low risk. Child failures now become observable earlier. The existing
readiness failure, timeout, collision classification, and cleanup owners
remain in place.
- The tests hold real filesystem boundaries through real child events.
They do not model a full hosted deployment.

## Model Used

- OpenAI Codex (GPT-6 family), with code execution and repository tools.
The runtime does not expose the exact serving model identifier or
context window size.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-11-06 17:50:22 -06:00
..
…
…
…
…
…