keep the three open threads the comms channel was holding
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
# Open threads after per-user Claude
|
||||
|
||||
Three things found on 2026-08-11/12 that are understood but not finished. They were written up in the
|
||||
`COMMS/sidecar-app-store` channel, which was deleted when the feature merged — this file is what survives.
|
||||
None of them blocks per-user Claude; all three were found while proving it worked.
|
||||
|
||||
---
|
||||
|
||||
## 1. The web terminal renders a long URL unreadably
|
||||
|
||||
**Half fixed.** `2a8f0049` added an OSC 52 handler, so a program's "press `c` to copy" now reaches the
|
||||
browser clipboard. That is the path a user is meant to take, and it works.
|
||||
|
||||
**Not fixed:** the URL itself renders as fragments. Claude Code's first-run login prints an OAuth URL of
|
||||
~400 characters; in the web terminal it appeared as scattered characters with large gaps (`h : l`), with
|
||||
nothing selectable or readable. On a normal terminal the same output wraps and reads fine.
|
||||
|
||||
Why it matters: first-run login is every new member's first five minutes, and the workaround was running
|
||||
`claude` under `tmux` on the server, capturing the pane, and reassembling the URL by hand across three wrapped
|
||||
lines. That is not something a member can be asked to do, and without OSC 52 there was no other way out.
|
||||
|
||||
Not diagnosed. What is known:
|
||||
|
||||
- the frontend loads `FitAddon`, `Unicode11Addon` and `WebLinksAddon`, and `allowProposedApi` is on
|
||||
- `cols`/`rows` are sent on connect (`Terminal.tsx`) and on resize, so it is not obviously a sizing problem
|
||||
- the pty gives `cols: Number(...) || 0` (`sidecar/pty/server.mjs:62`), so a client that omits them yields 0
|
||||
|
||||
Where I would start: capture the raw bytes the pty emits for that line and compare against what xterm renders.
|
||||
Either the TUI is positioning with escapes xterm handles differently, or the width the program believes it has
|
||||
disagrees with the width the terminal has.
|
||||
|
||||
## 2. Agent sessions do not survive a restart — one property behind three symptoms
|
||||
|
||||
Worth fixing as one thing, because it currently presents as three and invites three separate fixes:
|
||||
|
||||
- **Blast radius.** An unhandled rejection used to kill the agent sidecar and every live session with it.
|
||||
`8c4f150c` made that survivable, but any *real* restart still loses every session.
|
||||
- **The restart sweep must skip.** `endTurnIfAgentIsGone` asks the agent whether a session is really still
|
||||
generating. Scoped by `userId` since `d59adbf1`, so a session with no recorded `userId` has no safe identity
|
||||
to ask as and is skipped — correct, and it leaves that session marked generating.
|
||||
- **Stuck "generating".** The user-visible face of the above. A spinner that never resolves after an agent
|
||||
restart is this, not the UI.
|
||||
|
||||
The missing property is that a session does not survive a restart with its identity intact. Given that, the
|
||||
sweep would not need to skip, a restart would be an inconvenience rather than a loss, and the spinner would
|
||||
resolve itself.
|
||||
|
||||
## 3. `ProcessTransport is not ready for writing` — survivable, still unexplained
|
||||
|
||||
```
|
||||
error: ProcessTransport is not ready for writing
|
||||
at write (…/claude-agent-sdk/sdk.mjs)
|
||||
at streamInput (…/claude-agent-sdk/sdk.mjs)
|
||||
```
|
||||
|
||||
Four fatal crashes on 2026-08-11, one of which truncated a turn mid-sentence. There are **no frames from our
|
||||
code** — it is a floating rejection inside the SDK's own input pump, so no `await` of ours can catch it. With
|
||||
no handler registered it reached the top level and Bun exited, taking every session on the machine.
|
||||
|
||||
`8c4f150c` registered an `unhandledRejection` handler in `sidecar/claude/user-instance.ts`, which is the
|
||||
process `ecosystem.config.cjs` starts as `officer-agent`. Verified on Bun 1.3.9: the handler fires and the
|
||||
process survives. It has fired once in production since.
|
||||
|
||||
**The cause is still unknown.** Best hypothesis: the `claude` CLI exits while `streamInput` is still pumping,
|
||||
so the transport's `ready` flips false mid-write. Unconfirmed.
|
||||
|
||||
It no longer needs to be caught in the act — it needs someone to look after it happens. The next occurrence
|
||||
logs a full rejection in a *live* process with every other session still attached, which is a much better
|
||||
vantage point than a corpse.
|
||||
|
||||
Markers in `~/.pm2/logs/officer-agent-error.log`:
|
||||
|
||||
```
|
||||
grep -c 'Bun v1.3' → fatal exits. Was 4. A fifth means the backstop stopped working.
|
||||
grep -c 'UNHANDLED REJECTION' → caught and survived. Was 1.
|
||||
```
|
||||
|
||||
`uncaughtException` is deliberately not handled the same way: a rejection leaves the process's state intact,
|
||||
whereas a synchronous throw that unwound to the top supports no such claim, and continuing on a possibly
|
||||
corrupted heap is worse than restarting. That asymmetry is an argument for §2 rather than against itself.
|
||||
Reference in New Issue
Block a user