{"record":{"id":"2ebf49e5398f6398","repo":"mastra-ai/mastra","slug":"failed-to-delete-factory-res-status","errorCode":null,"errorMessage":"Failed to delete Factory (${res.status})","messagePattern":"Failed to delete Factory \\((.+?)\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mastracode/factory-ui/src/ui/domains/workspaces/services/github.ts","lineNumber":458,"sourceCode":"  const res = await fetch(\n    `${baseUrl}/web/factory/projects/${encodeURIComponent(factoryProjectId)}/repositories/${encodeURIComponent(projectRepositoryId)}`,\n    { method: 'DELETE', credentials: 'include', headers: { Accept: 'application/json' } },\n  );\n  if (!res.ok && res.status !== 404) throw new Error(`Failed to unlink repository (${res.status})`);\n}\n\n/**\n * Delete a Factory project. The server cascades over its source-control\n * connections (and their repository links). Missing projects are treated as\n * already deleted so removal stays idempotent.\n */\nexport async function deleteFactoryProject(baseUrl: string, factoryProjectId: string): Promise<void> {\n  const res = await fetch(`${baseUrl}/web/factory/projects/${encodeURIComponent(factoryProjectId)}`, {\n    method: 'DELETE',\n    credentials: 'include',\n    headers: { Accept: 'application/json' },\n  });\n  if (!res.ok && res.status !== 404) throw new Error(`Failed to delete Factory (${res.status})`);\n}\n\nexport interface CommitResult {\n  committed: boolean;\n}\n\n/** Stage and commit all changes in a Factory session workspace. */\nexport async function commitChanges(\n  baseUrl: string,\n  projectRepositoryId: string,\n  message: string,\n  sessionId: string,\n): Promise<CommitResult> {\n  return postRepositoryGitOp<CommitResult>(baseUrl, projectRepositoryId, 'commit', { message, sessionId });\n}\n\nexport interface PushResult {\n  pushed: boolean;","sourceCodeStart":440,"sourceCodeEnd":476,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/factory-ui/src/ui/domains/workspaces/services/github.ts#L440-L476","documentation":"deleteFactoryProject sends DELETE to `/web/factory/projects/{id}`; the server cascades deletion over the project's source-control connections. Any non-ok status except 404 throws this error — 404 is treated as success so removal is idempotent.","triggerScenarios":"Non-ok, non-404 response: 401 expired session, 403 current user is not the project owner, 409 project has active resources/runs blocking deletion, 400 invalid factoryProjectId, 5xx server error mid-cascade (partial deletion possible).","commonSituations":"Double-clicking delete (second call gets 404, silently fine); collaborator trying to delete a project they don't own; deleting from a stale UI list where the id no longer exists; self-hosted DB constraint failure during cascade.","solutions":["Check the status: 403 → only owners can delete; request ownership or have the owner delete","409 → stop dependent runs/resources first, then retry the delete","Re-fetch the project list after handling; if the project is gone, the 404-tolerant behavior already succeeded","For 5xx, verify project state on the server before retrying to detect partial cascades","Ensure the id passed is the Factory project id from the API, not a local or display identifier"],"exampleFix":"// before\nawait deleteFactoryProject(baseUrl, id);\nremoveFromList(id);\n// after\ntry {\n  await deleteFactoryProject(baseUrl, id);\n} catch (e) {\n  if (/\\(409\\)/.test(e.message)) {\n    await stopActiveRuns(id);\n    await deleteFactoryProject(baseUrl, id);\n  } else throw e;\n} finally {\n  queryClient.invalidateQueries(factoryProjectsKeys.all);\n}","handlingStrategy":"try-catch","validationCode":"function canAttemptFactoryDelete(factoryProjectId: unknown): boolean {\n  return typeof factoryProjectId === 'string' && factoryProjectId.length > 0;\n}","typeGuard":"const isAlreadyDeleted = (e: unknown): boolean => e instanceof Error && /\\(404\\)/.test(e.message);","tryCatchPattern":"try {\n  await deleteFactoryProject(baseUrl, factoryProjectId);\n} catch (e) {\n  if (isAlreadyDeleted(e)) {\n    // treat as success\n  } else if (/\\((401|403)\\)/.test(e.message)) {\n    promptReauthOrOwnershipCheck();\n  } else {\n    throw e;\n  }\n} finally {\n  queryClient.invalidateQueries(factoryProjectsKeys.all);\n}","preventionTips":["Confirm ownership before exposing the delete action (server returns 403 otherwise)","Stop dependent runs/resources before delete to avoid 409s","Invalidate the projects cache in a finally block regardless of outcome","Disable the delete button while a delete is in flight to avoid duplicate requests","Rely on the built-in 404 idempotency; don't add extra existence pre-checks that can race"],"tags":["http","fetch","factory","idempotency"],"backgroundTag":"http-delete-failed","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T08:17:16.595Z"}