{"record":{"id":"24a520541e9bd233","repo":"nexu-io/open-design","slug":"workspace-access-denied","errorCode":"WORKSPACE_ACCESS_DENIED","errorMessage":"the automation Workspace is no longer writable by this member","messagePattern":"the automation Workspace is no longer writable by this member","errorType":"error_code","errorClass":"AutomationWorkspaceScopeError","httpStatus":null,"severity":"error","filePath":"apps/daemon/src/automations/workspace-scope.ts","lineNumber":135,"sourceCode":"}\n\n/**\n * Re-authorize a Workspace/member pair captured when an unattended automation\n * was configured. No daemon-global current/active Workspace participates.\n */\nexport async function authorizePersistedAutomationWorkspaceScope(\n  scope: PersistedAutomationWorkspaceScope,\n  fetchWorkspaceDirectory: (() => Promise<WorkspaceDirectoryFetchResult>) | undefined,\n): Promise<WorkspaceCollabContext> {\n  const items = await fetchDirectoryOrThrow(fetchWorkspaceDirectory);\n  const item = items.find(\n    (candidate) =>\n      candidate.workspaceId === scope.workspaceId\n      && candidate.workspaceMemberId === scope.workspaceMemberId,\n  );\n  const context = activeWritableContext(item ? workspaceContextFromDirectoryItem(item) : null);\n  if (!context) {\n    throw new AutomationWorkspaceScopeError(\n      'WORKSPACE_ACCESS_DENIED',\n      'the automation Workspace is no longer writable by this member',\n      false,\n    );\n  }\n  return context;\n}\n\n/**\n * Resolve a reused project's persisted binding. The project row chooses the\n * Workspace; the signed-in directory supplies the current member and authority.\n */\nexport async function authorizePersistedProjectWorkspace(\n  workspaceIdInput: string,\n  fetchWorkspaceDirectory: (() => Promise<WorkspaceDirectoryFetchResult>) | undefined,\n): Promise<WorkspaceCollabContext> {\n  const workspaceId = workspaceIdInput.trim();\n  const items = await fetchDirectoryOrThrow(fetchWorkspaceDirectory);","sourceCodeStart":117,"sourceCodeEnd":153,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/automations/workspace-scope.ts#L117-L153","documentation":"Thrown by authorizePersistedAutomationWorkspaceScope with code WORKSPACE_ACCESS_DENIED and retryable=false. After fetching the directory, the persisted (workspaceId, workspaceMemberId) pair either is not present in the directory or activeWritableContext() returned null (member is inactive or lacks write permission). This is a permanent authorization state until an admin changes roles.","triggerScenarios":"The member was removed from the workspace, had their role downgraded below write, or the workspace was deactivated; a stale automation whose member id no longer maps to an active account.","commonSituations":"Org reshuffles; an automation created by a user who later left the team; sandbox/preview workspace that was archived.","solutions":["Have a workspace admin restore the member's write role or re-add them to the workspace.","Reassign the automation to a current active member with write access.","If the automation is obsolete, delete it rather than retrying (retryable=false)."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Before persisting, confirm the member is currently writable.\nasync function memberStillWritable(fetcher: () => Promise<{ ok: boolean; items: Array<{ workspaceId: string; workspaceMemberId: string; writable: boolean }> }>, scope: { workspaceId: string; workspaceMemberId: string }): Promise<boolean> {\n  const dir = await fetcher();\n  if (!dir.ok) return false;\n  const item = dir.items.find(i => i.workspaceId === scope.workspaceId && i.workspaceMemberId === scope.workspaceMemberId);\n  return Boolean(item && item.writable);\n}","typeGuard":null,"tryCatchPattern":"try {\n  await authorizePersistedAutomationWorkspaceScope(scope, fetcher);\n} catch (err) {\n  if (err instanceof AutomationWorkspaceScopeError && err.code === 'WORKSPACE_ACCESS_DENIED') {\n    // retryable === false: do NOT retry. Surface to the user for admin action.\n    return forbidden('You no longer have write access to this Workspace. Contact a workspace admin.');\n  }\n  throw err;\n}","preventionTips":["Do not retry WORKSPACE_ACCESS_DENIED — it is permanent until an admin changes roles.","Periodically reconcile persisted Workspace-scoped automations against the current directory so stale ones surface early.","When offboarding a member, reassign or delete their automations in the same change."],"tags":["workspace","authz","permission","automation"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}