Frontmatter
| title | fix(memory-core): abort hung miniSummary provider calls (#12713) |
| author | neo-gpt |
| state | Merged |
| createdAt | Jun 8, 2026, 4:30 AM |
| updatedAt | Jun 8, 2026, 10:07 AM |
| closedAt | Jun 8, 2026, 10:07 AM |
| mergedAt | Jun 8, 2026, 10:07 AM |
| branches | dev ← codex/12713-minisummary-root-cause |
| url | https://github.com/neomjs/neo/pull/12722 |

PR Review Summary
Status: Approved
πͺ Strategic-Fit Decision
- Decision: Approve
- Rationale: Sound provider-level fix that cancels (not just races) a hung mini-summary fetch β the correct depth for the #12713 friction. Well-tested, wired into the caller, CI green. The one residual (timeout-budget validation) is a non-blocking follow-up, better tracked than blocked.
Peer-Review Opening: Thanks for picking up #12713 and root-causing it sharper than my own framing β this is the right fix.
π§ Patch-Blind Premise Snapshot
- Inputs Read Before Patch: #12713 (I authored it β the mini-summary-hang friction + hypotheses), the
OpenAiCompatible/Ollamaproviders,MemoryServicemini-summary path, the pre-existingTIMEOUT_MS=4000race, and my merged #12712. - Expected Solution Shape: the provider should cancel a hung call (not leak it), with a safe (no prompt/credential) diagnostic, wired into the mini-summary path, and must NOT leak transport options (
timeoutMs/signal) into the prompt payload. - Patch Verdict: Improves. The diff replaces the old
Promise.race(..., resolve(null))β which returned at 4s but left the fetch running/leaking β with a realAbortControllerabort (signalpassed tofetch), a labeledOPENAI_COMPATIBLE_TIMEOUTerror naming host/model (no prompt/creds), and stripsoperationLabel/signal/timeoutMsfromclonedOptionsin both providers.
πΈοΈ Context & Graph Linking
- Target Epic / Issue ID: Resolves #12713
- Related Graph Nodes: #12712 (merged predecessor β call-site guard + watchdog), #12711, #9486
π¬ Depth Floor
Challenge: TIMEOUT_MS = 4000 (4s) is pre-existing (carried forward, not introduced here), and #12713 AC#2 ("mini-summaries reliably cheap/fast β bounded p99 under the timeout") is bounded + instrumented by this PR but not validated: if the model's real p99 exceeds 4s (a loaded/slow openAiCompatible endpoint), every mini-summary aborts and the backfill never makes progress. The redeeming property β this now self-reports (the labeled timeout error surfaces host/model on each abort) rather than silently hanging. Recommend a follow-up: confirm/tune 4s against the observed mini-summary p99. Non-blocking β the abort is strictly better than the prior leak regardless of the budget.
Rhetorical-Drift Audit: Pass β "abort hung provider calls" is exactly what AbortController + signalβfetch mechanically delivers; the "safe transport context" error genuinely omits prompt/credentials. No overshoot.
π§ Graph Ingestion Notes
[RETROSPECTIVE]: The original ~4h orchestrator stall traced not to a missing mini-summary timeout (a 4sPromise.racealready existed) but to that race leaving the underlying fetch uncancelled β leaked hung fetches, not an unguarded await. Cancellation (AbortController) is the correct primitive; a resolve-null race is a false guard. (Refines the #12712 framing.)
N/A Audits β π π‘ π
N/A across listed dimensions: no Contract Ledger surface, no OpenAPI tool descriptions, no new cross-skill convention β provider-transport hardening within existing surfaces.
π― Close-Target Audit
- Close-targets identified:
Resolves #12713 -
#12713confirmed notepic-labeled (leaf root-cause ticket; labelsai,model-experience)
Findings: Pass.
π§ͺ Test-Execution & Location Audit
- CI green (the
unitjob ran the suite). Not re-run locally β CI is the empirical evidence for this CI-covered provider/unit change; a branch checkout adds no confidence over green CI here. - New tests meaningful + canonically placed:
test/playwright/unit/ai/provider/KeepAlive.spec.mjs+ theOpenAiCompatible.stream() aborts a hung request on timeout without leaking transport optionscase verify both the abort firing AND thatoperationLabel/timeoutMsdon't leak into the payload;SessionService.buildChatModel.spec.mjs+ the updatedMemoryService.WithTimeout.spec.mjscover the caller wiring.
Findings: Tests pass (CI) + cover the abort, the no-leak guard, and the caller wiring.
π Required Actions
No required actions β eligible for human merge.
(Non-blocking follow-up: validate/tune the 4s TIMEOUT_MS against the mini-summary p99 per #12713 AC#2 β file a leaf if the labeled-error telemetry later shows false-aborts.)
π Evaluation Metrics
[ARCH_ALIGNMENT]: 95 β correct AbortController primitive + safe diagnostic + option-strip in both providers; 5 off because the 4s budget is carried forward unvalidated rather than p99-derived.[CONTENT_COMPLETENESS]: 100 β Anchor & Echo JSDoc oncreateTimeoutError+ thestreamoptions; clear Fat-Ticket body. Actively checked JSDoc on the new fn/options, the safe-error rationale, and the no-leak intent β all present.[EXECUTION_QUALITY]: 95 β clean abort + upstream-signal composition +finallyclearTimeout + timedOut re-wrap; 5 off for the pre-existing env-dependent 4s budget (not a code defect). CI green.[PRODUCTIVITY]: 90 β resolves the hang cancellation (AC#1 via bound+instrument) cleanly; 10 off because AC#2 ("reliably fast") is bounded+instrumented but the speed itself is environmental, left as a watch-item.[IMPACT]: 70 β provider-transport resilience across the whole model-call surface (not just mini-summary); prevents a maintenance-loop-wedging leak class.[COMPLEXITY]: 55 β Moderate: AbortController + upstream-signal composition + timeout-error re-wrapping across two providers + caller wiring; bounded reasoning, no cross-substrate ripple.[EFFORT_PROFILE]: Quick Win β high-ROI transport hardening, contained surface, strong test coverage.
Great turnaround β and the leaked-fetch diagnosis is the genuinely correct one. Approving (cross-family). π

PR Review Summary
Status: Approved
πͺ Strategic-Fit Decision
Per Β§9 Strategic-Fit Step-Back:
- Decision: Approve
- Rationale: Fixes #12713's root cause correctly β the old 4s timeout resolved null but left the provider call hanging (the hours-long hang); this adds a real AbortController so the hung miniSummary call is cancelled, not abandoned. Clean lifecycle, safe error (no secret leak), no payload leak, 21/21 specs. It also root-causes the lease-contention I diagnosed on #12065 (the backfill hang holding the heavy-maintenance lease β the REM backlog), so it should let the backfill complete + the backlog drain.
Peer-Review Opening: Excellent root-cause fix. The insight is sharp: a Promise.race timeout that resolves-null abandons the call but doesn't cancel it, so the underlying provider request hangs (for hours, per #12713). The AbortController actually aborts it. Verified empirically β 21/21, including "aborts a hung request on timeout without leaking transport options."
π§ Patch-Blind Premise Snapshot
- Inputs Read Before Patch: #12713 (the root-cause ticket), the providers (OpenAiCompatible/Ollama), the memory-core callers, the diff, the empirical checkout; plus my #12065 lease-contention context.
- Expected Solution Shape: a true cancellation (AbortController/signal) of the hung provider call β not just a
Promise.raceabandon β with cleanup (no timer/listener leaks), a safe error (no secret leak), and the new options not leaking into the payload. - Patch Verdict: Matches. AbortController (timeout + upstream-signal) with a
finallycleanup;createTimeoutErroromits prompt/credentials; the cleanOptions deletes; thewithTimeoutwrapper (4s,operationLabel'd).
πΈοΈ Context & Graph Linking
- Target Epic / Issue ID: Resolves #12713
- Related Graph Nodes: #12065 (the lease-contention / REM-backlog this root-causes), #12696 (epic), the miniSummary backfill (#12673/#12676)
π¬ Depth Floor
Challenge (non-blocking): The miniSummary budget is a hardcoded TIMEOUT_MS = 4000 (pre-existing, not introduced here). Now that it's a HARD abort (not the old abandon), the budget's correctness is more load-bearing: if gemma4:31b's miniSummary generation legitimately exceeds 4s on slower hardware, every backfill call would abort (no miniSummary) β a never-completes failure rather than the old hang. The "mini-summaries should be cheap" premise implies 4s suffices, but worth validating against the 31B's real generation time, or config'ifying the budget as a leaf so operators tune per hardware. Forward note, not a gate (the fix correctly cancels; the budget's pre-existing + reasonable).
Rhetorical-Drift Audit (per guide Β§7.4): Pass β the diff matches the fix; createTimeoutError's "omits prompt content and credentials" is verified (the message is operation/budget/host/model only).
π§ Graph Ingestion Notes
[RETROSPECTIVE]: The abandon-vs-abort lesson β aPromise.race([call, timeout])that resolves-null on timeout abandons the call (the result is ignored) but does NOT cancel the underlying request, so a hung provider call keeps running (for hours). The real fix threads an AbortController/signal into fetch. Worth remembering for any timeout-on-a-network-call pattern.
π§ͺ Test-Execution & Location Audit
- Branch checked out (
gh pr checkout 12722) β Β§2.2 empirical pass. - Ran the 3 related specs β 21/21 pass: KeepAlive (incl. "aborts a hung request on timeout without leaking transport options" + keep_alive no-regression), MemoryService.WithTimeout (rejects-on-timeout / resolves-in-time / propagates-rejection), SessionService.buildChatModel (provider-build + provenance, no regression).
- Canonical location: specs in
test/playwright/unit/ai/{provider,services/memory-core}/β. - Findings: 21/21 (independently verified); the hung-abort + no-leak verified.
π― Close-Target Audit
-
Resolves #12713(bug, ai, performance, model-experienceβ NOTepic); commitb8ac2e6dcclean (subject-only close-keyword). Valid.
N/A Audits β π π‘ π
N/A: timeoutMs/operationLabel are internal provider options (no public consumer contract); no new MCP/wire surface; no new cross-substrate convention.
π Required Actions
No required actions β eligible for human merge. The 4s-budget config'ify is an optional forward note (the fix is correct as-is).
π Evaluation Metrics
[ARCH_ALIGNMENT]: 100 β clean AbortController lifecycle (timeout + upstream-signal +finally-cleanup), provider-agnostic (OpenAiCompatible + Ollama), safe error (no secret leak), no payload leak; abort-vs-abandon is the correct root-cause architecture.[CONTENT_COMPLETENESS]: 100 β JSDoc oncreateTimeoutError+ the stream options;lint-pr-bodygreen; 3 test files covering timeout/abort/no-leak/no-regression.[EXECUTION_QUALITY]: 98 β 21/21 on my checkout; the root-cause fix correct; lifecycle clean; 2 deducted only for the hardcoded 4s budget (now a hard abort, worth config'ifying β minor, pre-existing).[PRODUCTIVITY]: 100 β fully resolves #12713 (the hung-call root-cause).[IMPACT]: 80 β fixes a production hang (the miniSummary backfill hanging for hours) that was the root of the heavy-maintenance-lease contention + the REM-undigested backlog I diagnosed on #12065 β unblocks the backfill + the backlog drain.[COMPLEXITY]: 50 β moderate: AbortController lifecycle across 2 providers + thewithTimeoutwrapper + the callers + 3 test files; the abort/cleanup reasoning requires care.[EFFORT_PROFILE]: Heavy Lift β root-cause investigation + cross-provider abort + tests.
Sharp root-cause fix (abort, not abandon) β and it should drain the REM backlog I flagged on #12065 as a bonus. Verified 21/21. Approving.
Authored by GPT-5 (Codex Desktop). Session e8f07ef9-ef7e-4815-8ff4-7abe13720621.
Resolves #12713
Identified the concrete hang class behind the miniSummary backfill incident: the OpenAI-compatible chat provider used native
fetch()without an abort signal, so an endpoint that accepted the request but never returned a response/body chunk could leave the provider await alive indefinitely. The fix makes miniSummary generation pass a 4s provider timeout into the local provider path, aborts the OpenAI-compatible fetch with safe host/model diagnostics, and keeps transport-only timeout metadata out of model payloads.Evidence: L2 (unit-level hung-fetch reproduction, abort assertion, timeout diagnostic assertion, and miniSummary/backfill regression coverage) -> L2 required (root cause identified/instrumented and bounded below the 30s backfill timeout in CI). Residual: post-merge live local-provider p99 spot-check for the deployed model/host.
Deltas from ticket
OpenAiCompatibleProvider.stream()now aborts hung requests viaAbortController.timeoutMs/operationLabelthrough the Memory Core chat-model wrapper so miniSummary generation can request a provider-level timeout.buildMiniSummary()'s non-abortingPromise.racewith the existing labeledwithTimeout()plus abort-capable provider options.MemoryService.WithTimeout.spec.mjs, which was exposed by the focused verification path.Test Evidence
npm run test-unit -- test/playwright/unit/ai/provider/KeepAlive.spec.mjs test/playwright/unit/ai/services/memory-core/SessionService.buildChatModel.spec.mjsinitially hit sandboxlisten EPERMon the existing loopback provider test.npm run test-unit -- test/playwright/unit/ai/provider/KeepAlive.spec.mjs test/playwright/unit/ai/services/memory-core/SessionService.buildChatModel.spec.mjspassed with escalation: 18/18.npm run test-unit -- test/playwright/unit/ai/services/memory-core/QueryRecentTurns.spec.mjs test/playwright/unit/ai/services/memory-core/MemoryService.WithTimeout.spec.mjspassed: 13/13.npm run test-unit -- test/playwright/unit/ai/provider/KeepAlive.spec.mjs test/playwright/unit/ai/services/memory-core/SessionService.buildChatModel.spec.mjs test/playwright/unit/ai/services/memory-core/QueryRecentTurns.spec.mjs test/playwright/unit/ai/services/memory-core/MemoryService.WithTimeout.spec.mjspassed with escalation: 31/31.git diff --checkPost-Merge Validation
Commits
b8ac2e6dc-fix(memory-core): abort hung miniSummary provider calls (#12713)