gitbutlerapp/gitbutler · error

Invalid token format

Error message

Invalid token format

What it means

LoginService.confirmPasswordReset requires the 2xx response of POST sessions/confirm_new_password to carry a string `token` (the post-reset session token). When `data.token` is missing or not a string, the successHandler throws 'Invalid token format'. sendPostRequest converts the throw into an error LoginResponse (errorCode network_error), so the password reset flow reports failure even though the reset may have succeeded server-side.

Solutions

  1. Inspect the raw 2xx response body for sessions/confirm_new_password to see whether/where `token` is returned
  2. Check whether the backend still returns a session token on password reset; if it removed that behavior, update the client flow to not expect a token (e.g. require an explicit login afterwards)
  3. Align client/server versions or environments so the response contract matches
  4. If a token can legitimately be absent, treat it as optional and fall back to a login step instead of throwing

Example fix

// before
return { message: data.message, token: data.token };
// after
return {
  message: data.message,
  token: isStr(data.token) ? data.token : null,
}; // caller then falls back to loginWithEmail when token is null
Defensive patterns

Strategy: try-catch

Validate before calling

const res = await fetch(base + "sessions/confirm_new_password", { method: "POST", ... });
const data = await res.json();
if (res.ok && typeof data?.token !== "string") {
  console.warn("no session token in reset confirmation response", data);
}

Type guard

function hasResetToken(data: unknown): data is { token: string } {
  return typeof data === "object" && data !== null && typeof (data as any).token === "string";
}

Try / catch

const result = await loginService.confirmPasswordReset(token, newPassword, confirm);
if (result.type === "error" && result.errorMessage === "Invalid token format") {
  // Reset likely succeeded but no session token was returned; fall back to explicit login
  await loginService.loginWithEmail(email, newPassword);
  return;
}

Prevention

When it happens

Trigger: POST sessions/confirm_new_password returns HTTP 200 with a JSON body that lacks a string `token` field (or has a non-string value), tripping isStr(data.token) at line 137.

Common situations: Backend changed to no longer auto-login after reset (no token returned); server returns the token under a different key name; version mismatch between app and API; proxies or mocks that strip or reshape the success payload.

Understand the failure class

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18). Data as JSON: /api/errors/1b7ab58bea556692. Report an issue: GitHub.

Appendix: source

Thrown at packages/shared/src/lib/login/loginService.ts:137

			},
		);
	}

	async confirmPasswordReset(
		token: string,
		newPassword: string,
		passwordConfirmation: string,
	): Promise<LoginResponse<{ message: string; token: string }>> {
		return await this.sendPostRequest(
			"sessions/confirm_new_password",
			{
				password_reset_token: token,
				password: newPassword,
				password_confirmation: passwordConfirmation,
			},
			(data) => {
				if (!isStr(data.message)) throw new Error("Invalid message format");
				if (!isStr(data.token)) throw new Error("Invalid token format");
				return { message: data.message, token: data.token };
			},
		);
	}

	async resetPassword(email: string): Promise<LoginResponse<{ message: string }>> {
		return await this.sendPostRequest("sessions/forgot_password", { email }, (data) => {
			if (!isStr(data.message)) throw new Error("Invalid message format");
			return { message: data.message };
		});
	}

	async loginWithEmail(email: string, password: string): Promise<LoginResponse<string>> {
		return await this.sendPostRequest("sessions/login_with_email", { email, password }, (data) => {
			if (!isStr(data.token)) throw new Error("Invalid token format");
			return data.token;
		});
	}

View on GitHub (pinned to 58e5313667)