Repository navigation
pi-magic-context 0.48.0: dispatch fence escalates a superseded context pass into a turn refusal ("Pi context pass superseded before writer admission") #667
Description
Magic Context turns fail with "Pi context pass superseded before writer admission"
Plugin: @cortexkit/pi-magic-context v0.48.0
Repo: https://github.com/cortexkit/magic-context (packages/pi-plugin)
Observed on: omp 18.7.0, CachyOS Linux, plugin log /tmp/omp/magic-context/magic-context.log
Symptom
A user turn aborts with:
Error: Magic Context could not safely prepare this turn; send your message again.
stage=provider dispatch elapsed=54630ms recovery=no managed result; refused
(Pi context pass superseded before writer admission)
Resending the message clears it.
Verified code path
All line numbers are dist/index.js.
The string Pi context pass superseded before writer admission exists once, in
PiContextBudget.assertOwner (31959-31960):
const isCurrent = () => isOwned() && (budget.sideTurn || !active.ended);
budget.assertOwner = () => {
if (!isCurrent())
throw new PiContextSupersededError("Pi context pass superseded before writer admission");
};The user-visible message is assembled by refuse() (31849-31871), which assigns
both strings appearing in the report:
-
budget.recovery = "no managed result; refused"(31858) -
budget.stage = "provider dispatch"is set by the caller at 31926 -
message =${RETRY_MESSAGE} ${budget.diagnostic()} (${reason.message})`` (31861)
Critically, refuse() only produces user-visible output when isCurrent() is
true (31850-31853):
const refuse = (ctx, budget, reason, isCurrent) => {
if (!isCurrent()) {
log2(`[magic-context][pi] DISCARDED CONTEXT RESULT ${budget.diagnostic()}`, reason);
return; // no notify, no abort - silently inert
}
...
ctx.ui?.notify(message, "error");
pi.appendEntry(ENTRY_TYPE, { message });
ctx.abort();
};So the error reaching the user proves isCurrent() was true when it was
raised — the pass was not superseded from the perspective of the assertion
that produced it.
The actual throwing site
Log events matching the reported error (3 in the current log) carry this stack:
Error: Pi context pass superseded before writer admission
at <anonymous> (index.js:31279:19)
at <anonymous> (index.js:35932:7)
at assertOutcome (index.js:31038:10)
at assert (index.js:31043:10)
at <anonymous> (index.js:31068:7)
at async <anonymous> (index.js:35949:31)
Frame 31068 is inside injectM0M1Pi:
function injectM0M1Pi(state, db, piMessages, entryIds, recomputeM1ThisPass = false, passSnapshot) {
if (state.preparedPrefix) { ... }
const snapshot = passSnapshot ?? createPiM0M1PassSnapshot({ // <- 31067-31068
db,
sessionId: state.sessionId,
...
});and assert/assertOutcome (31038-31043) are the budget.assertOutcome
alias bound at 37784 (let assertCurrentPass = budget.assert). The pass guard
therefore throws inside the M0/M1 transform pipeline, i.e. during transform
work — not at the before_provider_request dispatch fence, and not in
flushPiServedArrayLedger.
This matters for the fix: the assertion is evaluating a newer pass while the
older pass is still doing transform work, because the transform runs long enough
(a transform completing in 630 ms over 1028 messages is typical here) for the
next turn's pass to start and replace operations.get(key).
Two distinct log classes — do not conflate them
| Class | Log string | Site | User-visible? |
|---|---|---|---|
| A. benign supersession | DISCARDED CONTEXT RESULT: pending Pi transform decision (76×) |
dynamic budget.diagnostic() at 31971 |
no |
| B. the reported bug | DISCARDED CONTEXT RESULT stage=provider dispatch elapsed=… recovery=no managed result; refused Pi context pass superseded… (3×) |
dynamic string at 31851, thrown from the transform pipeline | yes |
| C. genuine refusal | turn refused stage=provider dispatch elapsed=79ms recovery=no managed result; refused (1×) |
31976, error not a PiContextSupersededError |
yes |
| D. ledger flush | DISCARDED CONTEXT RESULT: queued served capture (5×) |
static string 33348, inside flushPiServedArrayLedger |
no |
Class D's static string looks like class B in a log grep, but it is a different
call site with a different cause and never reaches the user. Class B is the one
that produced the reported error.
Why it is intermittent
Reproduction conditions observed (not proven general):
- Sessions with a long context. The failing session was at
usage=46.5% (435645 tokens)withcontext_passes=270,side_contexts=1,
dispatch_fence=on. - The transform pipeline reaches a pass guard that asserts (
passGuard.assert()
appears at 25138, 25157, 25185, 25191, 25217, 25230, 25245, 25249, 25278,
25284, 25298, 25303, 31660, 31671, 38364) after a newer pass has already
replaced the operation. Whether that happens depends on whether a new pass
starts during the transform — hence the rarity (3 events against 76 benign
discards). elapsed=54630msis transform time. No provider request is made on this path,
so no provider, WARP-proxy, or network behaviour is involved.
Suggested fix
The failure is that a pass doing long transform work can be superseded by a
newer pass and, when the older pass's next guard assertion fires, the error is
routed into refuse() and surfaced as a turn failure instead of being treated
as an abandoned pass.
The cleanest fix is in the transform pipeline's guard handling: once a pass is no
longer isCurrent(), its remaining work should unwind quietly (the newer pass
owns the session) rather than escalating into refuse(). Concretely, wrap the
transform pipeline so that a PiContextSupersededError raised after the pass has
been superseded is caught and discarded:
try {
return injectM0M1Pi(state, db, piMessages, entryIds, recomputeM1ThisPass, passSnapshot);
} catch (error) {
if (error instanceof PiContextSupersededError && !budget.isCurrent()) {
log2(`[magic-context][pi] abandoned superseded pass: ${state.sessionId}`);
return undefined; // let the newer pass win
}
throw error;
}An alternative (cheaper, less precise) is to give the older pass an abort signal
at supersession time so the guard raises a cancellation instead of a
supersession error — but that still needs the caller to distinguish
"cancelled because a newer pass won" from a real failure.
Notes for the reporter
stage=provider dispatchin the message is a label assigned at 31926, not
proof a provider call happened.- Class D (
flushPiServedArrayLedger, 33348) discards served-ledger lines and is
a separate, benign mechanism; fixing it is not required for this bug. - Log:
/tmp/omp/magic-context/magic-context.log(14 MB). Store:
~/.local/share/cortexkit/magic-context/context.db.
Activity
Qiiks commentedon Oct 12, 2026
Correction: the fix belongs in the dispatch fence, not the transform
Two reviewers pushed back on my original fix and they are right. Correcting the
analysis here — the original report below is updated inline where needed.
The decisive fact
stage = "provider dispatch"is assigned in exactly one place indist/index.js, at line 31926:diagnostic()only interpolatesthis.stage, so every log line or user messagecarrying
stage=provider dispatchwas assembled from a budget whose.stagewas set at 31926 — inside the
before_provider_requestfence catch(31915-31927). That catch is unconditional:
So the transform-pipeline frames in the stack trace show where the error was
thrown, not where the user-visible refusal was assembled. My original report
conflated the two, and my original fix is therefore ineffective.
Second error:
refuse()reached the notify path, soisCurrent()was TRUErefuse()(31849-31863):The logged class-B lines contain
recovery=no managed result; refused, and thatstring is only assigned at 31858 — i.e. inside the branch that continues to
ctx.ui.notify()at 31861 andctx.abort(). The!isCurrent()branch at31850-31853 returns before assigning
recovery. So the failing events took thenotify path, meaning
isCurrent()returned true at the fence.That also corrects the table in the original report: the log line at 31851 is the
inert branch (31851 logs the dynamic
${budget.diagnostic()}and returns), andclass B's user-visible behaviour belongs to 31861, not 31851.
Why my transform-pipeline fix cannot work
The fence does not re-read the transform's outcome. Its first assertion is
budget.assertOwner()(31913), which throws the samePiContextSupersededErroron the same!active.endedcondition. WrappinginjectM0M1Pito swallow that error changes nothing about what the fence sees —the same budget object still fails
assertOwner()at 31913 and still takes theunconditional catch. Swallowing inside the transform leaves the fence fully
intact, so the user is still notified and aborted.
The fix
Break the escalation at the fence. A superseded pass is an abandoned pass, never
a turn refusal:
Keeping
active.refused = trueinside the early-return branch would also bedefensible, but the essential change is that
refuse()— and thereforectx.ui.notify()andctx.abort()— is not reached for a supersession.Evidence for the reproduction conditions
All three logged instances have the same shape: the line
OMP context counts session=… context_passes=N dispatch_fence=onimmediatelyprecedes the failure, and in two of three a
checkout claim: WARNING check incomplete, admitting reason=…line precedesthat, with
reason=timeoutorreason=error stage=connect detail="Error: server pro….718 such checkout-stall warnings exist in the same log, so a stall alone is not
sufficient — the pass must then find itself non-current at the fence.
Original report (retained below, with the noted corrections applied)
[-]pi-magic-context 0.48.0: turn refused with "Pi context pass superseded before writer admission" raised from the M0/M1 transform pipeline[/-][+]pi-magic-context 0.48.0: dispatch fence escalates a superseded context pass into a turn refusal ("Pi context pass superseded before writer admission")[/+]magic-alfonso commentedon Oct 12, 2026
Thanks for the detailed trace and for correcting it yourself. We're reproducing this on a real Oh My Pi host now. One constraint shapes the fix: the dispatch fence exists so that OMP never sends a request Magic Context didn't prepare. So the fence can't simply return when a pass turns out to be superseded. It has to confirm that the request about to go out is the newer pass's managed result, and only then let it through. We'll check where the superseded error is raised, why the fence still saw the pass as current, and why that pass ran for 54 s. We'll post what we find here.
magic-alfonso commentedon Oct 12, 2026
Fixed on master; it ships in the next release.
Nothing was actually superseded. Oh My Pi delivers the previous run's
agent_endto extensions late, after the next run's context pass has already returned its managed result. The dispatch fence treated that late end as ending the current run, so the next request was refused with the superseded message. We reproduced it on real Oh My Pi 18.7.0 and 18.8.6 hosts with the released 0.48.0 build. A second, rarer path produced the same text: a pass that waited for the database past its deadline could start late and claim the session, making the newer pass look superseded.What changed:
agent_endfrom an earlier run no longer ends the current run's pass. A run's own end still invalidates its result, including when the run is aborted before its request goes out.Your stack trace matches 0.47.0 line numbers rather than 0.48.0, so your log mixes events from both versions. The 0.47.0 checkout-claim check that preceded two of your three events is already gone in 0.48.0, but the bug reproduces without it.
magic-alfonso commentedon Oct 13, 2026
Fixed in 0.49.0. A context pass that is replaced by a newer one no longer causes the newer, prepared turn to be refused, including when the previous turn ends late. An unreadable message list now produces a visible refusal instead of an error escaping the context hook. Update
@cortexkit/pi-magic-contextand restart Oh My Pi.