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

GitLab Duo Workflow direct_access failed with HTTP ${respons

Error message

GitLab Duo Workflow direct_access failed with HTTP ${response.status}: ${message} (or, when the body carries no message: GitLab Duo Workflow direct_access failed with HTTP ${response.status})

What it means

Thrown when the GitLab Duo Workflow direct_access endpoint returns a non-2xx status. If the response body carries a JSON message it is appended; otherwise the status alone is embedded. Per the source comment, the message always embeds 'HTTP <status>' so the streaming auth-retry path (extractStatusFromAssistantError → extractHttpStatusFromError) can detect the status and refresh/rotate broker credentials on 401/429 instead of failing hard.

Source

Thrown at packages/ai/src/providers/gitlab-duo-workflow.ts:1664

		},
		body: JSON.stringify(buildGitLabDuoWorkflowDirectAccessBody(rootNamespaceId, projectId, workflowDefinition)),
		signal: gitLabDuoWorkflowRestSignal(signal),
	});
	traceGitLabDuoWorkflow("direct_access.response", {
		status: response.status,
		ok: response.ok,
		rootNamespaceId,
		hasProjectId: Boolean(projectId),
	});
	if (!response.ok) {
		const message = await readGitLabDuoWorkflowResponseErrorMessage(response);
		// Always embed the HTTP status, even when the body carries a message: the
		// streaming auth-retry/rotation path (`extractStatusFromAssistantError` ->
		// `extractHttpStatusFromError`) refreshes/rotates broker credentials only
		// when the assistant error exposes `errorStatus` or the message embeds an
		// `HTTP <status>` token. A 401 `{"message":"Unauthorized"}` or a 429 quota
		// body would otherwise surface as a hard failure with no recoverable status.
		throw new AIError.GitLabDuoWorkflowApiError(
			message
				? `GitLab Duo Workflow direct_access failed with HTTP ${response.status}: ${message}`
				: `GitLab Duo Workflow direct_access failed with HTTP ${response.status}`,
			response.status,
		);
	}
	const payload = (await response.json()) as GitLabDirectAccessResponse;
	const token = extractGitLabWorkflowToken(payload);
	if (!token) {
		throw new AIError.ProviderResponseError("GitLab Duo Workflow direct_access did not return credentials", {
			provider: "gitlab-duo-agent",
			kind: "empty-body",
		});
	}
	traceGitLabDuoWorkflow("direct_access.token", { hasToken: true });
	const serviceEndpoint = !payload.gitlab_rails?.token && Boolean(payload.duo_workflow_service?.base_url);
	return {
		token,

View on GitHub (pinned to 9690622007)

Solutions

  1. Check the HTTP status in the message: 401/403 → re-issue the GitLab token with api scope and Duo Workflow entitlement
  2. The auth-retry path only fires when the status is embedded — if you catch this error, inspect errorStatus and retry with refreshed credentials
  3. For 429, back off and retry after the rate-limit window
  4. For 5xx, check GitLab service status and retry later

Example fix

// before: stale token
apiKey: process.env.OLD_GITLAB_TOKEN
// after: freshly minted token with api scope + Duo Workflow enabled
apiKey: process.env.GITLAB_TOKEN
Defensive patterns

Strategy: try-catch

Validate before calling

// Preflight entitlement/token check:
const res = await fetch(`${gitlabBaseUrl}/api/v4/user`, { headers: { authorization: `Bearer ${token}` } });
if (res.status === 401) throw new Error("GitLab token invalid — re-issue with api scope");

Type guard

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

Try / catch

try {
  await runGitLabDuoWorkflow(model, context, options, state);
} catch (err) {
  if (err instanceof AIError.GitLabDuoWorkflowApiError) {
    // message embeds `HTTP <status>` so extractHttpStatusFromError works:
    const status = err.status;
    if (status === 401) await refreshBrokerCredentials(); // then retry
    else if (status === 429) await Bun.sleep(rateLimitBackoff);
    else throw err;
  } else throw err;
}

Prevention

When it happens

Trigger: POST to the Duo Workflow direct_access credentials endpoint returns 401 (expired/invalid GitLab token), 403 (insufficient Duo Workflow entitlement), 429 (rate limited), or 5xx.

Common situations: GitLab personal access token expired or lacking api scope; GitLab tier without Duo Workflow enabled; GitLab.com rate limiting; GitLab instance version mismatch with the direct_access route.

Related errors


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