can1357/oh-my-pi · error · AIError.DevinApiError

Devin auth error ${response.status} ${response.statusText}:

Error message

Devin auth error ${response.status} ${response.statusText}: ${new TextDecoder().decode(payload)}

What it means

Thrown by fetchDevinAuthMetadata when the GetUserJwt HTTP call returns a non-2xx status. The response body bytes are decoded as text and embedded in the message so the caller can see the server's reason. It is a DevinApiError carrying the HTTP status code for upstream classification (e.g. auth retry logic).

Source

Thrown at packages/ai/src/providers/devin.ts:505

	apiKey: string | undefined,
	baseUrl: string,
	fetchImpl: NonNullable<StreamOptions["fetch"]>,
	signal: AbortSignal | undefined,
): Promise<{ userJwt: string; baseUrl?: string }> {
	const request = create(GetUserJwtRequestSchema, { metadata: create(MetadataSchema, devinCliMetadata(apiKey)) });
	const response = await fetchImpl(`${baseUrl}${DEVIN_AUTH_PATH}`, {
		method: "POST",
		headers: {
			"content-type": "application/proto",
			"connect-protocol-version": "1",
			accept: "*/*",
		},
		body: toBinary(GetUserJwtRequestSchema, request),
		signal,
	});
	const payload = new Uint8Array(await response.arrayBuffer());
	if (!response.ok) {
		throw new AIError.DevinApiError(
			`Devin auth error ${response.status} ${response.statusText}: ${new TextDecoder().decode(payload)}`,
			response.status,
		);
	}
	const decoded = decodeDevinUnaryMessage(GetUserJwtResponseSchema, payload);
	if (!decoded?.userJwt) {
		throw new AIError.ProviderResponseError("Devin auth error: GetUserJwt returned an empty user JWT", {
			provider: "devin",
			kind: "runtime",
		});
	}
	const customBaseUrl = decoded.customApiServerUrl.trim();
	return { userJwt: decoded.userJwt, ...(customBaseUrl ? { baseUrl: customBaseUrl.replace(/\/+$/, "") } : undefined) };
}

/**
 * Resolve a server-side router (`adaptive`) into the concrete model uid plus the
 * assignment JWT that authorizes it. The router uid is never a legal

View on GitHub (pinned to 9690622007)

Solutions

  1. Check that the Devin API key is valid and not revoked/expired; re-provision it
  2. Verify the Devin base URL matches your instance (self-hosted vs SaaS)
  3. Call GetUserJwt manually (curl) to read the embedded response body message
  4. If 5xx, retry later or check Devin service status

Example fix

// before
process.env.DEVIN_API_KEY = "stale-key"
// after
process.env.DEVIN_API_KEY = "<freshly-rotated devin api key>"
Defensive patterns

Strategy: try-catch

Validate before calling

// Preflight: verify the credential works before the real call
const res = await fetch(`${devinBaseUrl}/auth`, { headers: { authorization: `Bearer ${apiKey}` } });
if (!res.ok) throw new Error(`Devin credential rejected: ${res.status} — rotate the API key`);

Type guard

function isDevinApiError(err: unknown): err is InstanceType<typeof AIError.DevinApiError> {
  return err instanceof AIError.DevinApiError && typeof err.status === "number";
}

Try / catch

try {
  const meta = await fetchDevinAuthMetadata(baseUrl, request, signal);
} catch (err) {
  if (err instanceof AIError.DevinApiError && (err.status === 401 || err.status === 403)) {
    // refresh/rotate the Devin API key, then retry once
  } else throw err;
}

Prevention

When it happens

Trigger: POST to Devin's GetUserJwt endpoint responds 401/403/404/500 etc., with the raw body included — typically invalid or revoked Devin API key, wrong base URL, or server-side outage.

Common situations: Expired or rotated Devin credentials still configured in the environment; Devin instance URL misconfigured; Devin service outage or version change altering the auth route.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/a50e58f0e22d0025. Report an issue: GitHub.