hcengineering/platform · error · PlatformError

InternalServerError

InternalServerError

Error message

platform.status.InternalServerError

What it means

Thrown when the remove-account operation's decoded token lacks the initiator account or workspace fields. The token should carry both the acting account and the workspace; their absence indicates a malformed or wrong-kind token, so the service throws InternalServerError instead of continuing.

Source

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

export async function leaveWorkspace (
  ctx: MeasureContext,
  db: AccountDB,
  branding: Branding | null,
  token: string,
  params: { account: AccountUuid }
): Promise<LoginInfo | null> {
  const { account: targetAccount } = params

  if (targetAccount == null || targetAccount === '') {
    throw new PlatformError(new Status(Severity.ERROR, platform.status.BadRequest, {}))
  }

  const { account, workspace, extra } = decodeTokenVerbose(ctx, token)
  ctx.info('Removing account from workspace', { account, workspace })

  if (account == null || workspace == null) {
    ctx.error('Account or workspace not provided for leaving', { account, workspace })
    throw new PlatformError(new Status(Severity.ERROR, platform.status.InternalServerError, {}))
  }

  const initiatorRole = await db.getWorkspaceRole(account, workspace)
  const targetRole = await db.getWorkspaceRole(targetAccount, workspace)

  if (account !== targetAccount) {
    if (initiatorRole == null || getRolePower(initiatorRole) < getRolePower(AccountRole.Maintainer)) {
      ctx.error("Need to be at least maintainer to remove someone else's account from workspace", {
        account,
        workspace,
        initiatorRole
      })
      throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
    }

    if (targetRole === AccountRole.Owner && initiatorRole === AccountRole.Maintainer) {
      ctx.warn('Maintainer cannot remove owner from workspace', {
        account,

View on GitHub (pinned to 63e28dc964)

Solutions

  1. Obtain a workspace-scoped token for the target workspace before the call.
  2. Decode the token (decodeTokenVerbose equivalent) and verify account and workspace claims are present.
  3. Re-login/re-select the workspace to refresh the token.
  4. Align client and server versions so token claims match.

Example fix

// before: any token
await accountClient.removeFromWorkspace(anyToken, { account: target })
// after: workspace token with required claims
const claims = decodeTokenVerbose(ctx, token)
if (claims.account && claims.workspace) {
  await accountClient.removeFromWorkspace(token, { account: target })
}
Defensive patterns

Strategy: type-guard

Validate before calling

const { account, workspace } = decodeTokenVerbose(ctx, token)
if (account == null || workspace == null) {
  throw new Error('Token must be workspace-scoped with account and workspace claims')
}

Type guard

function isWorkspaceToken(t: { account?: string | null; workspace?: string | null }): t is { account: string; workspace: string } {
  return t.account != null && t.workspace != null
}

Try / catch

try {
  await accountClient.removeFromWorkspace(token, { account: target })
} catch (err) {
  if (err instanceof PlatformError && err.status.code === platform.status.InternalServerError) {
    await reLoginToWorkspace(workspace) // refresh workspace-scoped token
  } else throw err
}

Prevention

When it happens

Trigger: Passing a token to removeFromWorkspace whose payload has account == null or workspace == null (e.g. a non-workspace token, or token issued without workspace binding).

Common situations: Using a global/login token instead of a workspace-scoped token; corrupted token; version mismatch where the workspace claim was renamed or removed.

Related errors


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