hcengineering/platform · error · PlatformError

account.status.WorkspaceNotFound

account.status.WorkspaceNotFound

Error message

WorkspaceNotFound

What it means

WorkspaceNotFound is thrown by batchAssignWorkspacePermission when decodeTokenVerbose returns workspace === null, i.e. the provided token is not bound to any workspace. The operation requires a workspace-scoped token to know which workspace's permissions to assign.

Source

Thrown at server/account/src/operations.ts:3435

  return subscription
}

export async function batchAssignWorkspacePermission (
  ctx: MeasureContext,
  db: AccountDB,
  branding: Branding | null,
  token: string,
  params: {
    accountIds: AccountUuid[]
    permission: string
  }
): Promise<void> {
  const { accountIds, permission } = params
  const { account, workspace } = decodeTokenVerbose(ctx, token)

  if (workspace === null) {
    throw new PlatformError(new Status(Severity.ERROR, platform.status.WorkspaceNotFound, { workspaceUuid: workspace }))
  }

  const accRole = account === systemAccountUuid ? AccountRole.Owner : await db.getWorkspaceRole(account, workspace)
  if (accRole == null || getRolePower(accRole) < getRolePower(AccountRole.Maintainer)) {
    throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
  }

  await db.batchAssignWorkspacePermission(workspace, accountIds, permission)
}

export async function batchRevokeWorkspacePermission (
  ctx: MeasureContext,
  db: AccountDB,
  branding: Branding | null,
  token: string,
  params: {
    accountIds: AccountUuid[]
    permission: string

View on GitHub (pinned to 63e28dc964)

Solutions

  1. Obtain a workspace-scoped token (e.g. login with workspace selection or use selectWorkspace) before calling.
  2. Check that the target workspace still exists and the token was issued for it.
  3. Re-issue/refresh the token if it predates workspace deletion or migration.

Example fix

// before
await accountClient.batchAssignWorkspacePermission(loginToken, { accountIds, permission })
// after
const wsToken = await accountClient.selectWorkspace(loginToken, workspaceUuid)
await accountClient.batchAssignWorkspacePermission(wsToken, { accountIds, permission })
Defensive patterns

Strategy: try-catch

Validate before calling

const role = await getMyWorkspaceRole(workspaceUuid)
if (role === null || getRolePower(role) < getRolePower(AccountRole.Maintainer)) {
  throw new Error('Maintainer role required to read subscription')
}

Type guard

function hasMaintainerRole(role: AccountRole | null): boolean {
  return role !== null && getRolePower(role) >= getRolePower(AccountRole.Maintainer)
}

Try / catch

try {
  const subscription = await client.fetchSubscription(token)
} catch (err) {
  if (isPlatformError(err) && err.status.code === account.status.Forbidden) {
    // surface 'requires Maintainer role' UI state or re-auth as admin
  }
  throw err
}

Prevention

When it happens

Trigger: Calling batchAssignWorkspacePermission with a personal/account-level token that carries no workspace claim (workspace resolves to null).

Common situations: Using a login token instead of a workspace token; workspace was deleted so the token's workspace claim is gone; constructing a token manually without the workspace field; env/region mixups yielding a token from a non-workspace endpoint.

Related errors


AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29). Data as JSON: /api/errors/17bbd772c7f20134. Report an issue: GitHub.