{"record":{"id":"e1818ca34b19b87d","repo":"different-ai/openwork","slug":"workspace-endpoint-is-unavailable-attachments-cou-e1818c","errorCode":null,"errorMessage":"Workspace endpoint is unavailable; attachments could not be copied for tool access.","messagePattern":"Workspace endpoint is unavailable; attachments could not be copied for tool access\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"apps/app/src/react-app/domains/session/sync/draft-parts.ts","lineNumber":51,"sourceCode":"  const toAbsolutePath = (path: string) => {\n    const trimmed = path.trim();\n    if (!trimmed) return \"\";\n    if (trimmed.startsWith(\"/\")) return trimmed;\n    if (/^[a-zA-Z]:[\\\\/]/.test(trimmed)) return trimmed;\n    if (!root) return \"\";\n    return joinWorkspaceRelativePath(root, trimmed);\n  };\n\n  const filenameFromPath = (path: string) => {\n    const normalized = path.replace(/\\\\/g, \"/\");\n    const segments = normalized.split(\"/\").filter(Boolean);\n    return segments[segments.length - 1] ?? \"file\";\n  };\n\n  const attachmentFileById = new Map<string, FilePartInput>();\n  if (draft.attachments.length > 0) {\n    if (!endpoint) {\n      throw new Error(\"Workspace endpoint is unavailable; attachments could not be copied for tool access.\");\n    }\n    const uploaded = await composerAttachmentsToWorkspaceFileParts({\n      attachments: draft.attachments,\n      endpoint,\n      sessionId,\n      workspaceRoot: root,\n    });\n    for (const part of uploaded) {\n      if (part.type === \"text\") {\n        parts.push(part);\n        continue;\n      }\n    }\n    const fileParts = uploaded.filter((part): part is FilePartInput => part.type === \"file\");\n    for (const [index, attachment] of draft.attachments.entries()) {\n      const filePart = fileParts[index];\n      if (filePart) attachmentFileById.set(attachment.id, filePart);\n    }","sourceCodeStart":33,"sourceCodeEnd":69,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/apps/app/src/react-app/domains/session/sync/draft-parts.ts#L33-L69","documentation":"draftToParts converts a composer draft into message parts; when the draft has attachments it must have a workspace endpoint to upload them through. If endpoint is null/undefined while draft.attachments is non-empty, it throws this error before delegating to composerAttachmentsToWorkspaceFileParts.","triggerScenarios":"Calling draftToParts (e.g. via parts()) with a draft containing attachments but endpoint = null — typically sending a saved draft in a context without an active workspace endpoint (workspace closed, cloud session not provisioned, or store restored before connect).","commonSituations":"Restoring a persisted draft with attachments after app restart before the workspace connects; sending from a stale store after workspace switch; headless/automation callers passing no endpoint while drafts still carry attachments.","solutions":["Resolve the workspace endpoint before converting/sending any draft that has attachments; strip or defer attachments if no endpoint exists.","Ensure the draft store revalidates attachments against the current workspace on restore.","Catch the error and prompt to reconnect the workspace before sending."],"exampleFix":"// before: await draftToParts(draft, root, null, sessionId) | // after: const endpoint = resolveEndpoint(workspaceId); if (draft.attachments.length && !endpoint) { throw new Error('Reconnect the workspace before sending attachments'); } await draftToParts(draft, root, endpoint, sessionId)","handlingStrategy":"validation","validationCode":"if (draft.attachments.length > 0 && !endpoint) { showToast('Reconnect the workspace before sending attachments'); return; } await draftToParts(draft, root, endpoint, sessionId);","typeGuard":"function canSendDraft(endpoint: unknown, draft: { attachments: unknown[] }): boolean { return draft.attachments.length === 0 || endpoint != null; }","tryCatchPattern":"try { await draftToParts(draft, root, endpoint, sessionId); } catch (e) { if (String(e.message).includes('Workspace endpoint is unavailable')) { await connectWorkspace(); } else { throw e; } }","preventionTips":["Resolve the workspace endpoint before loading/restoring drafts that carry attachments.","Strip or quarantine attachments on drafts restored without an active endpoint.","Keep draft stores workspace-scoped so stale drafts cannot send cross-workspace."],"tags":["attachments","draft","endpoint","precondition"],"backgroundTag":"missing-workspace-endpoint","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}