{"record":{"id":"3c851a91f625c375","repo":"paperclipai/paperclip","slug":"ingest-operation-did-not-return-an-issue-id-the-d","errorCode":null,"errorMessage":"Ingest operation did not return an issue id; the dropped file could not be attached.","messagePattern":"Ingest operation did not return an issue id; the dropped file could not be attached\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/plugins/plugin-llm-wiki/src/ui/issue-attachments.ts","lineNumber":14,"sourceCode":"type FetchLike = (input: string, init: RequestInit) => Promise<Response>;\n\nexport type IngestSourceActionResult = {\n  operation?: {\n    issue?: {\n      id?: unknown;\n    } | null;\n  } | null;\n};\n\nexport function readIngestOperationIssueId(result: unknown): string {\n  const issueId = (result as IngestSourceActionResult | null)?.operation?.issue?.id;\n  if (typeof issueId === \"string\" && issueId.trim()) return issueId;\n  throw new Error(\"Ingest operation did not return an issue id; the dropped file could not be attached.\");\n}\n\nasync function readUploadError(response: Response): Promise<string> {\n  const body = await response.json().catch(() => null);\n  if (body && typeof body === \"object\") {\n    const error = (body as { error?: unknown; message?: unknown }).error;\n    if (typeof error === \"string\" && error.trim()) return error;\n    const message = (body as { error?: unknown; message?: unknown }).message;\n    if (typeof message === \"string\" && message.trim()) return message;\n  }\n  return `Attachment upload failed with HTTP ${response.status}.`;\n}\n\nexport async function uploadIssueAttachmentFile(input: {\n  companyId: string;\n  issueId: string;\n  file: File;\n  fetchImpl?: FetchLike;","sourceCodeStart":1,"sourceCodeEnd":32,"githubUrl":"https://github.com/paperclipai/paperclip/blob/67001ec6eb96ae601aa27bc91d9b2415d665334a/packages/plugins/plugin-llm-wiki/src/ui/issue-attachments.ts#L1-L32","documentation":"Thrown by readIngestOperationIssueId() when the result of an ingest source action does not contain a usable operation.issue.id string. The LLM Wiki issue-attachment UI uses this to attach a dropped file to the issue created by an ingest operation; without a valid issue id the attachment has no target. The function accepts unknown and narrows result.operation.issue.id, requiring a non-empty trimmed string.","triggerScenarios":"Calling readIngestOperationIssueId() on a result where the ingest operation has not yet created an issue (async not complete). The backend returned a result shape missing operation.issue.id (API contract drift). The ingest action errored but returned a 2xx with no issue ref.","commonSituations":"Backend version mismatch where the ingest result schema changed. Race: caller reads the result before the issue row is committed. A custom ingest source plugin that does not populate operation.issue.id.","solutions":["Verify the ingest operation actually created an issue before calling readIngestOperationIssueId() (await the operation to completion).","Check the backend API contract for the ingest source action; ensure it returns operation.issue.id as a non-empty string.","If the ingest legitimately produces no issue, do not call this reader — branch on result shape first."],"exampleFix":"// before\nconst id = readIngestOperationIssueId(ingestResult); // throws if id missing\n// after\nconst raw = (ingestResult as any)?.operation?.issue?.id;\nif (typeof raw === \"string\" && raw.trim()) {\n  const id = raw.trim();\n  await uploadIssueAttachmentFile({ companyId, issueId: id, file });\n} else {\n  // handle: ingest did not produce an issue yet\n}","handlingStrategy":"type-guard","validationCode":"function tryReadIssueId(result: unknown): string | null {\n  const id = (result as any)?.operation?.issue?.id;\n  return typeof id === \"string\" && id.trim() ? id.trim() : null;\n}\nconst id = tryReadIssueId(ingestResult);\nif (!id) { /* handle: no issue yet */ }","typeGuard":"function hasIngestIssueId(r: unknown): r is { operation: { issue: { id: string } } } {\n  const id = (r as any)?.operation?.issue?.id;\n  return typeof id === \"string\" && id.trim().length > 0;\n}","tryCatchPattern":"try {\n  const id = readIngestOperationIssueId(result);\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"did not return an issue id\")) {\n    // wait/retry ingest, or surface 'no issue created' to user\n  } else throw err;\n}","preventionTips":["Await the ingest operation to completion before reading its result.","Validate the backend ingest contract returns operation.issue.id.","Use a type guard instead of the throwing reader when no-issue is a legitimate state."],"tags":["llm-wiki","attachments","ingest","validation"],"backgroundTag":null,"analyzedSha":"67001ec6eb96ae601aa27bc91d9b2415d665334a","analyzedAt":"2026-08-12T12:05:45.408Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}