{"record":{"id":"a8684751e7f160ed","repo":"can1357/oh-my-pi","slug":"aborted","errorCode":null,"errorMessage":"aborted","messagePattern":"aborted","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"info","filePath":"packages/coding-agent/src/internal-urls/issue-pr-protocol.ts","lineNumber":518,"sourceCode":"\treturn {\n\t\turl: url.href,\n\t\tcontent,\n\t\tcontentType: \"text/markdown\",\n\t\tsize: Buffer.byteLength(content, \"utf-8\"),\n\t\tnotes: [freshness, `File listing for pr://${repo}/${parsed.number}`],\n\t};\n}\n\n/**\n * Handler for `issue://` URLs.\n */\nexport class IssueProtocolHandler implements ProtocolHandler {\n\treadonly scheme = \"issue\";\n\treadonly immutable = true;\n\n\tasync resolve(url: InternalUrl, context?: ResolveContext): Promise<InternalResource> {\n\t\tif (context?.signal?.aborted) {\n\t\t\tthrow new Error(\"aborted\");\n\t\t}\n\t\tconst parsed = parseUrl(url, \"issue\");\n\t\tif (parsed.kind === \"list\") {\n\t\t\ttry {\n\t\t\t\treturn await fetchAndRenderList(\"issue\", parsed, url, context);\n\t\t\t} catch (err) {\n\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\tthrow new Error(`issue:// listing failed: ${message}`);\n\t\t\t}\n\t\t}\n\t\t// parseUrl already rejects `issue://.../diff`; this guard is a belt-and-\n\t\t// suspenders catch in case the union grows.\n\t\tif (parsed.kind !== \"single\") {\n\t\t\tthrow new Error(`Invalid issue:// URL: unexpected variant '${parsed.kind}'`);\n\t\t}\n\t\ttry {\n\t\t\tconst lookup = await getOrFetchIssue({\n\t\t\t\tcwd: resolveCwd(context),","sourceCodeStart":500,"sourceCodeEnd":536,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/internal-urls/issue-pr-protocol.ts#L500-L536","documentation":"IssueProtocolHandler.resolve (packages/coding-agent/src/internal-urls/issue-pr-protocol.ts:518) checks the caller-supplied AbortSignal before doing any work and throws a plain Error('aborted') if context.signal.aborted is already true. It is the handler's cooperative-cancellation contract: a resolve() call made with an already-cancelled signal refuses to start. It is an Error, not a DOMException, so callers must match on message rather than error name.","triggerScenarios":"Calling issueProtocolHandler.resolve(url, { signal }) where the passed AbortSignal was already aborted (e.g. AbortController.abort() fired before or while enqueueing the resolution), such as when a chat turn is cancelled and pending internal-URL lookups are flushed with aborted signals.","commonSituations":"User cancels/clears a session turn in the TUI while issue:// links are queued for resolution; a timeout aborts the controller before the handler runs; a race between abort and the async queue dispatch.","solutions":["Treat this as a benign cancellation, not a failure: catch it and skip/ignore the resolution result.","Check signal.aborted before calling resolve() and skip the call entirely when it is already true.","Create a fresh AbortController per resolution if you intend the lookup to proceed despite an earlier cancellation elsewhere.","If you need typed cancellation, wrap the throw site (or catch site) to convert it into a DOMException('aborted','AbortError')."],"exampleFix":"// before\nconst resource = await handler.resolve(url, { signal: sharedController.signal });\n// after\nif (sharedController.signal.aborted) return; // skip aborted lookups\nconst resource = await handler.resolve(url, { signal: sharedController.signal });","handlingStrategy":"try-catch","validationCode":"// Skip the call entirely when already cancelled:\nif (context?.signal?.aborted) return null; // do not invoke resolve()","typeGuard":"function isAbortError(err: unknown): err is Error {\n  return err instanceof Error && (\n    err.message === \"aborted\" || err.name === \"AbortError\"\n  );\n}","tryCatchPattern":"try {\n  const res = await issueHandler.resolve(url, { signal });\n} catch (err) {\n  if (err instanceof Error && err.message === \"aborted\") return; // benign cancellation\n  throw err;\n}","preventionTips":["Check signal.aborted before dispatching queued resolutions.","Match on the exact message 'aborted' (not error name) — this handler throws plain Error.","Use one AbortController per logical user action; abort it once, and treat all 'aborted' throws as success-path no-ops.","Never log cancelled lookups as failures; filter them from error metrics."],"tags":["abort","cancellation","abortsignal","async"],"backgroundTag":"operation-aborted","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}