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

  1. Reconnect the channel in Postiz (the disconnect already happened server-side) to establish fresh tokens
  2. Check the provider's developer portal for revoked permissions or expired app secrets
  3. Verify the OAuth client secret/id env vars for that provider are still valid
  4. 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

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

Related errors


AI-assisted analysis of gitroomhq/postiz-app@0f1647f749 (2026-08-27). Data as JSON: /api/errors/607b8560f752091b. Report an issue: GitHub.