Context
The deferred sibling-half of the #12837 / #12833 split (with @neo-opus-grace). #12833 added a wall-clock timeout + degrade-on-timeout to SessionService.summarizeSession, and surfaced that consumerFrictionHelper.categorizeInvocationError discriminates a timeout symptom by message-regex (/timeout|aborted|timed[ -]out/i). #12818 exported PROVIDER_TIMEOUT_CODE (ai/provider/createTimeoutError.mjs:61) precisely so callers detect a provider timeout structurally, without per-provider string knowledge. @neo-opus-grace adopted it on the ask-path (SearchService, #12832 / #12844); this is the sibling adoption at the shared consumerFrictionHelper site.
Deferred from #12833's PR to keep that PR scoped, and because touching the file inherits a check-ticket-archaeology grandfathered-ref cleanup (below).
The Problem
categorizeInvocationError returns 'timeout' only when the sanitized message matches the regex. The provider's PROVIDER_TIMEOUT error (both OpenAiCompatible + Ollama, post-#12818) carries error.code === PROVIDER_TIMEOUT_CODE — the authoritative, message-wording-independent signal. The friction consumers (SemanticGraphExtractor, SessionService) therefore mis-bucket a provider timeout whose message ever drifts from "timed out" → 'parse-failure', silently mis-routing the degrade/friction logic. Non-blocking today (the message matches), but brittle.
The Architectural Reality
ai/services/memory-core/helpers/consumerFrictionHelper.mjs categorizeInvocationError(err) — add if (err?.code === PROVIDER_TIMEOUT_CODE) return 'timeout'; BEFORE the regex (structural-first, regex fallback). Import PROVIDER_TIMEOUT_CODE from ../../../provider/createTimeoutError.mjs.
- Precedent to mirror:
SearchService.#createDegradedSynthesisResponse (#12832 / #12844) — isTimeout = error?.code === PROVIDER_TIMEOUT_CODE || /timed out/i.test(reason) + a regex-independent test.
- Husky scoping note: staging
consumerFrictionHelper.mjs + consumerFrictionHelper.spec.mjs trips buildScripts/util/check-ticket-archaeology.mjs on ~6 pre-existing #N-in-comment refs (not added by this work). Resolve via the hook-sanctioned // ticket-ref-ok: <reason> markers (or a focused comment-ref cleanup). This is why the change was deferred from #12833 rather than bundled.
Decision Record impact
aligned-with the #12818 PROVIDER_TIMEOUT contract (no ADR governs the provider-error surface).
Acceptance Criteria
Out of Scope
- The SessionService degrade-on-timeout (shipped, #12833) + the ask-path adoption (#12832 / #12844).
- Re-designing the
check-ticket-archaeology whole-file comment scan — a separate friction (it blocks edits to any file with grandfathered comment-refs; worth its own MX look, not bundled here).
Avoided Traps
- Removing the regex fallback (keep it for non-coded error paths).
- Rewriting others' grandfathered comments (prefer the sanctioned
ticket-ref-ok marker over a prose rewrite).
Related
- #12833 / #12837 — SessionService stall fix (this is its deferred sibling-half)
- #12832 / #12844 — ask-path
PROVIDER_TIMEOUT_CODE adoption (@neo-opus-grace; the mirror)
- #12818 / #12814 — the
PROVIDER_TIMEOUT_CODE provider contract
Authored by @neo-opus-vega (Claude Opus 4.8).
Context
The deferred sibling-half of the #12837 / #12833 split (with @neo-opus-grace). #12833 added a wall-clock timeout + degrade-on-timeout to
SessionService.summarizeSession, and surfaced thatconsumerFrictionHelper.categorizeInvocationErrordiscriminates atimeoutsymptom by message-regex (/timeout|aborted|timed[ -]out/i). #12818 exportedPROVIDER_TIMEOUT_CODE(ai/provider/createTimeoutError.mjs:61) precisely so callers detect a provider timeout structurally, without per-provider string knowledge. @neo-opus-grace adopted it on the ask-path (SearchService, #12832 / #12844); this is the sibling adoption at the sharedconsumerFrictionHelpersite.Deferred from #12833's PR to keep that PR scoped, and because touching the file inherits a
check-ticket-archaeologygrandfathered-ref cleanup (below).The Problem
categorizeInvocationErrorreturns'timeout'only when the sanitized message matches the regex. The provider'sPROVIDER_TIMEOUTerror (bothOpenAiCompatible+Ollama, post-#12818) carrieserror.code === PROVIDER_TIMEOUT_CODE— the authoritative, message-wording-independent signal. The friction consumers (SemanticGraphExtractor,SessionService) therefore mis-bucket a provider timeout whose message ever drifts from "timed out" →'parse-failure', silently mis-routing the degrade/friction logic. Non-blocking today (the message matches), but brittle.The Architectural Reality
ai/services/memory-core/helpers/consumerFrictionHelper.mjscategorizeInvocationError(err)— addif (err?.code === PROVIDER_TIMEOUT_CODE) return 'timeout';BEFORE the regex (structural-first, regex fallback). ImportPROVIDER_TIMEOUT_CODEfrom../../../provider/createTimeoutError.mjs.SearchService.#createDegradedSynthesisResponse(#12832 / #12844) —isTimeout = error?.code === PROVIDER_TIMEOUT_CODE || /timed out/i.test(reason)+ a regex-independent test.consumerFrictionHelper.mjs+consumerFrictionHelper.spec.mjstripsbuildScripts/util/check-ticket-archaeology.mjson ~6 pre-existing#N-in-comment refs (not added by this work). Resolve via the hook-sanctioned// ticket-ref-ok: <reason>markers (or a focused comment-ref cleanup). This is why the change was deferred from #12833 rather than bundled.Decision Record impact
aligned-withthe #12818PROVIDER_TIMEOUTcontract (no ADR governs the provider-error surface).Acceptance Criteria
categorizeInvocationErrorreturns'timeout'whenerr.code === PROVIDER_TIMEOUT_CODE, regardless of message wording; the message-regex remains a fallback for non-coded errors.'timeout'), mirroring #12832's ask-path test.check-ticket-archaeologypasses.consumerFrictionHelperspec is green.Out of Scope
check-ticket-archaeologywhole-file comment scan — a separate friction (it blocks edits to any file with grandfathered comment-refs; worth its own MX look, not bundled here).Avoided Traps
ticket-ref-okmarker over a prose rewrite).Related
PROVIDER_TIMEOUT_CODEadoption (@neo-opus-grace; the mirror)PROVIDER_TIMEOUT_CODEprovider contractAuthored by @neo-opus-vega (Claude Opus 4.8).