gitroomhq/postiz-app · critical · HttpException
Channel disconnected due to expired token
Error message
Channel disconnected due to expired token
What it means
After invoking a provider tool, the controller attempts to refresh the channel's OAuth credentials; if the refresh yields no data (refresh token revoked, expired, or the app was disconnected upstream), the integration is automatically disconnected via disconnectChannel and a 401 'Channel disconnected due to expired token' is thrown. This is a destructive side effect: the integration is removed as part of handling the failure.
Source
Thrown at apps/backend/src/public-api/routes/v1/public.integrations.controller.ts:613
getIntegration.token,
body.data || {},
getIntegration.internalId,
getIntegration
);
return { output: result };
} catch (err) {
if (err instanceof RefreshToken) {
const data = await this._refreshIntegrationService.refresh(
getIntegration
);
if (!data) {
await this._integrationService.disconnectChannel(
org.id,
getIntegration
);
throw new HttpException(
{ msg: 'Channel disconnected due to expired token' },
401
);
}
const { accessToken } = data;
if (accessToken) {
getIntegration.token = accessToken;
if (integrationProvider.refreshWait) {
await timer(10000);
}
continue;
}
}
throw new HttpException({ msg: 'Unexpected error' }, 500);View on GitHub (pinned to 0f1647f749)
Solutions
- Reconnect the channel in Postiz (the disconnect already happened server-side) to establish fresh tokens
- Check the provider's developer portal for revoked permissions or expired app secrets
- Verify the OAuth client secret/id env vars for that provider are still valid
- Before calling tools, check the integration still exists (it won't after this fires) and alert owners on 401 rather than retrying
Example fix
// before
try {
await triggerTool(integration.id, 'sendPost', {...});
} catch (e) { /* retries forever against a dead channel */ }
// after
try {
await triggerTool(integration.id, 'sendPost', {...});
} catch (e) {
if (e.status === 401) {
// channel was auto-disconnected: notify owner to reconnect, do NOT retry
await notifyOwner('Channel disconnected, please reconnect');
return;
}
throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
const integration = (await listIntegrations()).find((i) => i.id === id);
if (!integration) throw new Error('Channel already disconnected — reconnect required'); Type guard
const isTokenRefreshFailure = (e: unknown): boolean => (e as any)?.status === 401 && /expired token/i.test(JSON.stringify((e as any)?.response ?? (e as any)?.body ?? ''));
Try / catch
try {
await triggerTool(id, method, args);
} catch (e: any) {
if (e?.status === 401) {
// server already disconnected the channel: alert owner, stop retrying
await notifyOwnerReconnectRequired(id);
return;
}
throw e;
} Prevention
- Treat 401 from tool endpoints as terminal, never retry it
- Monitor provider developer-portals for revoked app permissions and secret expiry
- Surface reconnection prompts to channel owners before tokens lapse (e.g. long-lived Instagram/Facebook tokens)
When it happens
Trigger: POST to triggerIntegrationTool on a channel whose OAuth access/refresh token is expired and cannot be refreshed (password changed on the platform, app authorization revoked, refresh token invalid), causing the provider refresh to return null.
Common situations: Long-lived integrations whose tokens lapse without a valid refresh token; user revoked the app in the channel's security settings; platform policy change invalidated refresh tokens (e.g. Instagram/Facebook re-auth requirements); sandbox app credentials rotated.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- This integration requires an external URL and is not support
- Failed to generate auth URL
- Organization not found
- Integration not allowed
- Integration not allowed
AI-assisted analysis of gitroomhq/postiz-app@0f1647f749 (2026-08-27).
Data as JSON: /api/errors/607b8560f752091b.
Report an issue: GitHub.