jlcodes99/cockpit-tools · error

403 Forbidden

Error message

403 Forbidden

What it means

The account store's quota refresh flow detects that the backend marked the account's quota as forbidden (quota.is_forbidden) and throws '403 Forbidden' so the UI can render the refresh as a failure (red cross). It is a deliberate UI-signal exception, not an HTTP-level error, though it usually mirrors a real upstream 403 from the provider.

Source

Thrown at src/stores/useAccountStore.ts:416

                    }
                }
                return {
                    currentAccount:
                        nextByTarget[target]?.id === accountId
                            ? updatedAccount
                            : state.currentAccount?.id === accountId
                              ? updatedAccount
                              : state.currentAccount,
                    currentAccountsByTarget: nextByTarget,
                };
            });

            // 如果后端返回了配额错误信息,需要抛出异常让 UI 捕获并显示为失败(红叉)
            if (updatedAccount.quota_error) {
                throw new Error(updatedAccount.quota_error.message);
            }
            if (updatedAccount.quota?.is_forbidden) {
                throw new Error("403 Forbidden");
            }
        } catch (e) {
            // Token 级别失败(如 invalid_grant 会改变 disabled 状态):全量刷新确保数据正确
            // 如果是我们自己 throw 的配额错误,因为状态已经局部更新,不再需要全量刷新
            const isQuotaError = e instanceof Error && (
                get().accounts.find(a => a.id === accountId)?.quota_error?.message === e.message ||
                e.message === "403 Forbidden"
            );
            if (!isQuotaError) {
                await get().fetchAccounts();
            }
            throw e;
        } finally {
            await get().fetchCurrentAccount(target);
        }
    },

    refreshAllQuotas: async (trigger) => {

View on GitHub (pinned to 1ed8b77992)

Solutions

  1. Catch this error in the refresh caller and surface the account as failed/forbidden in the UI
  2. Re-authenticate the account (update its token / re-login) so upstream 403 stops occurring
  3. Check the account's quota_error field for the more specific upstream reason
  4. Delete and re-import the account if credentials are unrecoverable

Example fix

// before
await refreshQuota(accountId); // throws '403 Forbidden' uncaught
// after
try {
  await refreshQuota(accountId);
} catch (e) {
  markAccountFailed(accountId, (e as Error).message); // show red cross in UI
}
Defensive patterns

Strategy: try-catch

Validate before calling

const acct = get().accounts.find(a => a.id === accountId);
const forbidden = acct?.quota?.is_forbidden === true || !!acct?.quota_error;

Type guard

function isQuotaForbidden(a?: { quota?: { is_forbidden?: boolean } | null }) {
  return a?.quota?.is_forbidden === true;
}

Try / catch

try {
  await refreshQuota(accountId);
} catch (e) {
  if ((e as Error).message === '403 Forbidden') {
    // mark account forbidden in UI
  }
}

Prevention

When it happens

Trigger: Refreshing an account's quota when the backend's updated account payload has quota.is_forbidden === true (e.g. the upstream API rejected the token with 403), or when a quota_error message is present (that branch throws first).

Common situations: Account token revoked or banned upstream; account disabled by the provider; IP/region blocked by the provider; stale credentials after password change.

Understand the failure class

Related errors


AI-assisted analysis of jlcodes99/cockpit-tools@1ed8b77992 (2026-09-05). Data as JSON: /api/errors/b8b8f54a7a87c0d4. Report an issue: GitHub.