[Pi/OMP] A busy-database refusal kills a subagent, because nothing resends its turn (0.45.0) #640
Description
What happened
When another process holds the shared context.db write lock, Magic Context refuses the turn, as designed since #601. A refused turn of a subagent spawned by OMP's task tool is never resent. The only later turns are OMP's own "no tool call" reminders, and they are refused the same way. After three reminders OMP disposes the subagent, and the parent gets status="cancelled" with the reason Interrupted by user.
Our magic-context.log from 2026-10-08, 07:44 to 14:57 UTC, combined with the OMP transcripts:
| count | |
|---|---|
turn refused log lines |
51 |
| Sessions with at least one refusal | 22 (16 subagents, 6 primary) |
| Subagents that ended aborted after refusals | 6, in 3 projects |
The six subagents that died:
| Subagent | Assistant turns before the first refusal | Fallback outcome logged for its refusals |
|---|---|---|
| T13ControlSeam | 0 (new) | lkg_miss ×4 |
| T14PanelAdapter | 0 (new) | lkg_miss ×4 |
| MomusS02Round2 | 32 | lkg_model_mismatch ×4, raw_fallback_refused ×1 |
| T13SettingsCentre | 287 | lkg_content_mismatch, raw_fallback_refused |
| T5SupplyModels | 71 | lkg_content_mismatch, raw_fallback_refused |
| T12PanelConfig | 6 | lkg_invalidated_reshape ×3, raw_fallback_refused ×1 |
Five of them made no successful provider request after their first refusal, and every later assistant message is an empty stopReason: "aborted". T12PanelConfig got one more turn through, then died on later refusals.
One subagent, start to finish
T13ControlSeam is session 01a11bee-6b1a-7461-8914-bd2ef3c107d4. It was spawned at 14:32:52 together with one sibling, T14PanelAdapter, which failed the same way.
14:34:52.584Z transform: usage=4.2% (39108 tokens, limit=922000) decision=execute
14:34:52.838Z sqlite writer site=BEGIN IMMEDIATE lane=foreground acquire_ms=252 attempts=1 outcome=busy
14:34:52.838Z pi m[0] HARD fold pre-execution failed: SQLite writer acquisition remained busy
14:34:52.839Z TRANSIENT STORAGE FAILURE SQLITE_BUSY: LKG unavailable (lkg_miss); refusing unreduced 3-message input
14:34:52.839Z [pi] turn refused Magic Context storage is busy; send your message again
14:35:14.433Z TRANSIENT STORAGE FAILURE SQLITE_BUSY: LKG unavailable (lkg_miss); refusing unreduced 3-message input
14:35:52.656Z TRANSIENT STORAGE FAILURE SQLITE_BUSY: LKG unavailable (lkg_miss); refusing unreduced 5-message input
14:36:15.373Z TRANSIENT STORAGE FAILURE SQLITE_BUSY: LKG unavailable (lkg_miss); refusing unreduced 7-message input
Its transcript records:
- The assignment, as a user message.
- A
magic-context-turn-refusedentry, then an empty assistant message withstopReason: "aborted". - OMP's reminders 1, 2 and 3, each answered by the same refusal entry and an aborted message.
session_exit {reason: "dispose"}at 14:36:25.
The subagent used 0 tokens.
These write transactions ran during that window:
14:35:46.292Z lane=background hold_ms=2491 slow write transaction: site=message_index_incremental
14:35:48.963Z lane=background hold_ms=1065 slow write transaction: site=message_index_reconcile
14:35:51.165Z lane=foreground hold_ms=2190 slow write transaction: site=pi_compaction_queue
context.db is 1.6 GB. In this window OMP processes in three projects were writing to it.
Why this differs from #601
#601 was closed as fixed in 0.45.0 with this policy: wait at most 250 ms for the database, then resend the last good request or refuse the turn. The refusal was kept on purpose, because "a refusal costs one resend." Unreduced history could overflow the window and costs two prompt-cache rebuilds.
That holds when a person is watching and resends. It fails for a subagent:
- Nobody resends. The host's reminders are the only later turns. They arrive while contention is still going on, and each one is refused. The refusal ends the subagent's assignment, and with it the parent's plan step. Turns that the host starts in a primary session have the same problem. The parent's next turn after receiving the failed subagents' results was refused at 14:36:59, and that session then sat idle until the user restarted OMP at 14:42.
- Often there is no usable last-good request. A new subagent has none (
lkg_miss). In the mid-run cases above the replay was rejected for a model mismatch, a content mismatch or a reshape, or it did not fit (raw_fallback_refused completeness=partial). - A new subagent has nothing to protect. On its first pass nothing has been dropped, the input is 3 messages at 4.2% of the window, and there is no cached prefix. Passing that input through cannot cause the overflow or cache loss that [Pi] PiStorageBusyError on SQLite lock contention triggers fail-closed ctx.abort(), blocking interactive turns #601 guards against.
Expected
A transient SQLITE_BUSY should not permanently kill a subagent. Possible fixes, in our order of preference:
- Fail open when there is nothing to protect. If a session has no last-good request and no applied drops or compartments, pass
event.messagesthrough instead of refusing. - Wait longer when nobody will resend. For subagent sessions (an OMP subagent's session header has
parentSession), retry the writer with a longer backed-off budget before refusing. - Shorten foreground holds.
pi_compaction_queueheld the foreground lane for 2.2 s, against a 250 ms budget for everyone else.
The 0.46.0 notes shorten several lock holds, which should make this rarer. They do not change the refusal path for sessions without a usable last-good request. We are still on 0.45.0, because the v95 migration needs every OMP process stopped at once.
Environment
- Linux 6.18, x86_64
- OMP (oh-my-pi) 18.8.5
@cortexkit/pi-magic-context0.45.0context.db1.6 GB, shared by concurrent OMP processes in several projects- Subagents spawned in parallel as background jobs with OMP's
tasktool
Activity
magic-alfonso commentedon Oct 8, 2026
Thanks for the detailed report and the log tables. They make the failure clear: a refused subagent turn is never resent, and many of these subagents had no usable last-good request to fall back on.
We're reproducing this on a real OMP host now and checking each of the fallback outcomes you listed (
lkg_miss,lkg_model_mismatch,lkg_content_mismatch,lkg_invalidated_reshape,raw_fallback_refused). Some of those may be rejecting a replay that would have been safe, especially for subagents that had dozens or hundreds of turns. We're also looking at the 2.2 spi_compaction_queuehold. I'll follow up here once we know which of your three proposals is safe to adopt.magic-alfonso commentedon Oct 9, 2026
Fixed in 0.47.0. When another process holds the database, the turn now resends the last good request if it still fits. Otherwise it waits up to about 16.5 seconds for the database, within Oh My Pi's 30-second extension limit. Two OMP-only mismatches that made a valid last good request look unusable are fixed. A turn that is still refused says what it was waiting for, and the next reminder completes it. OpenCode 1 and 2 also try the last good request before waiting.
Update to 0.47.0 (OpenCode:
opencode-magic-context@0.47.0; Pi and Oh My Pi:@cortexkit/pi-magic-context@0.47.0). There is no database migration, so restarting the host is enough. OpenCode 2 needs 2.0.22 or newer and does not update plugins on its own: open/plugins, select Magic Context and press ctrl+u. Release notes: https://github.com/cortexkit/magic-context/releases/tag/v0.47.0