hcengineering/platform · error · PlatformError
Forbidden
Forbidden
Error message
Forbidden
What it means
Forbidden is thrown by getPendingWorkspace when the decoded token's extra service claim is not exactly 'workspace'. This internal operation is reserved for the workspace service; any other service (e.g. 'tool', 'account', user tokens) is rejected before the pending-workspace query runs. It is a service-to-service authorization check, not a user permission failure.
Source
Thrown at server/account/src/serviceOperations.ts:273
* on every progress update.
* If no progress is reported for the workspace during this time,
* it will become available again to be processed by another executor.
*/
export async function getPendingWorkspace (
ctx: MeasureContext,
db: AccountDB,
branding: Branding | null,
token: string,
params: {
region: string
version: Data<Version>
operation: WorkspaceOperation
}
): Promise<WorkspaceInfoWithStatus | undefined> {
const { region, version, operation } = params
const { extra } = decodeTokenVerbose(ctx, token)
if (extra?.service !== 'workspace') {
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
}
const wsLivenessDays = getMetadata(accountPlugin.metadata.WsLivenessDays)
const wsLivenessMs = wsLivenessDays !== undefined ? wsLivenessDays * 24 * 60 * 60 * 1000 : undefined
const result = await db.getPendingWorkspace(region, version, operation, processingTimeoutMs, wsLivenessMs)
if (result != null) {
ctx.info('getPendingWorkspace', {
workspaceId: result.uuid,
workspaceName: result.name,
dataId: result.dataId,
mode: result.status.mode,
operation,
region,
major: result.status.versionMajor,
minor: result.status.versionMinor,
patch: result.status.versionPatch,View on GitHub (pinned to 63e28dc964)
Solutions
- Obtain/sign the token with extra.service === 'workspace' (use the workspace service's own credentials/keys).
- Check token issuance config so the service claim is set correctly for internal calls (not defaulted to another service).
- If you are another service, call the operation through the workspace service instead of directly.
- Decode the token locally (decodeTokenVerbose) and assert extra.service before making the call to fail fast.
Example fix
// before
const token = generateToken(ctx, 'account', { service: 'tool' })
await accountClient.getPendingWorkspace(ctx, token, params)
// after
const token = generateServiceToken(ctx, 'account', { service: 'workspace' }) // must be the workspace service
await accountClient.getPendingWorkspace(ctx, token, params) Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-check the service claim before calling
const { extra } = decodeTokenVerbose(ctx, token)
if (extra?.service !== 'workspace') {
throw new Error('getPendingWorkspace requires a workspace-service token')
} Type guard
function isWorkspaceServiceToken(extra: TokenExtra | undefined): extra is TokenExtra & { service: 'workspace' } {
return extra?.service === 'workspace'
} Try / catch
try {
return await getPendingWorkspace(params)
} catch (err) {
if (err instanceof PlatformError && err.status.code === platform.status.Forbidden) {
throw new Error('Token service must be "workspace"; re-mint token with correct service claim')
}
throw err
} Prevention
- Use the workspace service's dedicated credentials when signing internal tokens.
- Assert extra.service after decoding tokens in a shared auth helper.
- Never reuse user or tool tokens for workspace-internal endpoints.
- Add integration tests that assert token service claims for internal APIs.
When it happens
Trigger: Invoking getPendingWorkspace with a token whose extra.service != 'workspace' — e.g. using a tool-service token, an unauthenticated/user token with no service claim, or a token issued for the wrong service.
Common situations: Misconfigured service-to-service auth where the workspace worker reuses a generic service token; a new microservice calling workspace maintenance APIs with its own service claim; upgrading/rotating tokens and losing the 'workspace' service identifier; calling the endpoint from client-side code with a user token.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- platform.status.Forbidden
- Current user is not allowed to create trainings
- platform.status.Forbidden
- Forbidden
- account.status.Forbidden
AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29).
Data as JSON: /api/errors/d6dfd9bbec344713.
Report an issue: GitHub.