gitbutlerapp/gitbutler · error · ApiError
Login token expired. Please log in to GitButler again.
Error message
Login token expired. Please log in to GitButler again.
What it means
ApiError thrown by parseResponseJSON in the shared HttpClient (packages/shared/src/lib/network/httpClient.ts:114) whenever any JSON request (get/post/put/patch/delete) receives HTTP 401. It means the X-Auth-Token header sent by HttpClient.request is missing, invalid, or expired, and the session must be re-established. The Response object is attached to the ApiError for status inspection.
Source
Thrown at packages/shared/src/lib/network/httpClient.ts:114
async patch<T>(path: string, opts?: RequestOptions) {
return await this.requestJson<T>(path, { ...opts, method: "PATCH" });
}
async delete<T>(path: string, opts?: RequestOptions) {
return await this.requestJson<T>(path, { ...opts, method: "DELETE" });
}
async postRaw(path: string, opts?: RequestOptions) {
return await this.request(path, { ...opts, method: "POST" });
}
}
async function parseResponseJSON(response: Response) {
if (response.status === 204 || response.status === 205) {
return null;
} else if (response.status === 401) {
throw new ApiError("Login token expired. Please log in to GitButler again.", response);
} else if (response.status >= 400) {
const text = await response.text();
if (text.includes("401 Unauthorized") || text.includes("401 unauthorized")) {
throw new ApiError("Login token expired. Please log in to GitButler again.", response);
}
throw new ApiError(`HTTP Error ${response.statusText}: ${text}`, response);
} else {
return await response.json();
}
}
function formatBody(body?: FormData | object) {
if (!body) return;
return body instanceof FormData ? body : JSON.stringify(body);
}
View on GitHub (pinned to 2497b8007a)
Solutions
- Catch ApiError and check err.response.status === 401, then route the user to re-login
- Verify the token store feeding HttpClient still holds a token before authenticated calls
- Re-authenticate to mint a fresh token and retry the original request
- For long-lived sessions, refresh the token silently before it expires
Example fix
// before
const user = await httpClient.get("user");
// after
try {
const user = await httpClient.get("user");
} catch (err) {
if (err instanceof ApiError && err.response.status === 401) {
session.clear(); // drop stale token and start the login flow
return;
}
throw err;
} Defensive patterns
Strategy: try-catch
Validate before calling
import { get } from "svelte/store";
// run before any authenticated call
if (!get(httpClient.authenticationAvailable)) {
// no token at all: go through the login flow instead of guaranteed 401s
redirectToLogin();
} Type guard
function isExpiredSessionError(err: unknown): err is ApiError {
return err instanceof ApiError && err.response.status === 401;
} Try / catch
try {
await httpClient.get("user");
} catch (err) {
if (isExpiredSessionError(err)) {
await session.clearToken(); // drop the stale token
redirectToLogin(); // re-authenticate, then retry the original request
} else {
throw err;
}
} Prevention
- Check get(httpClient.authenticationAvailable) before authenticated calls
- Refresh or re-issue the session token before its TTL expires
- Clear the token store on logout in every tab to avoid revoked-token 401s
- Centralize the 401 handler once instead of catching per call site
When it happens
Trigger: Any authenticated HttpClient call after the token TTL expires; the token store is empty so no X-Auth-Token header is sent; the session was revoked server-side (password change, logout on another device); token present but malformed.
Common situations: App left open past token expiry and resumed from sleep; token store cleared (logout elsewhere, storage eviction); environment pointed at an API that expects a different auth scheme; clock skew causing premature expiry.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
AI-assisted analysis of gitbutlerapp/gitbutler@2497b8007a (2026-08-17).
Data as JSON: /api/errors/cd3131b52e45aa27.
Report an issue: GitHub.