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

GitLab Duo Workflow direct_access did not return credentials

Error message

GitLab Duo Workflow direct_access did not return credentials

What it means

Thrown when the direct_access endpoint returns HTTP 200 but the JSON payload contains no usable workflow token — extractGitLabWorkflowToken finds nothing in payload.gitlab_rails, payload.duo_workflow_service, etc. Without credentials the client cannot talk to the Duo Workflow service, so it fails with a ProviderResponseError (kind: empty-body).

Source

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

	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,
		...(serviceEndpoint && payload.duo_workflow_service?.base_url
			? { baseUrl: normalizeGitLabDuoWorkflowServiceBaseUrl(payload.duo_workflow_service.base_url) }
			: {}),
		headers: serviceEndpoint ? (payload.duo_workflow_service?.headers ?? {}) : {},
		serviceEndpoint,
	};
}

async function createGitLabDuoWorkflow(
	fetchImpl: FetchImpl,

View on GitHub (pinned to 9690622007)

Solutions

  1. Confirm Duo Workflow is enabled for your GitLab instance and project/namespace
  2. Compare your GitLab version's direct_access response shape with what the client expects; upgrade pi-ai or GitLab as needed
  3. Check for a proxy stripping the response body and bypass it
  4. Call direct_access manually with curl to inspect the raw JSON payload
Defensive patterns

Strategy: validation

Validate before calling

// Confirm Duo Workflow is provisioned before calling direct_access:
const res = await fetch(`${gitlabBaseUrl}/api/v4/profile`, { headers: { authorization: `Bearer ${token}` } });
const profile = await res.json();
if (!profile) throw new Error("GitLab token has no profile access — Duo Workflow likely unprovisioned");

Type guard

function hasWorkflowToken(p: unknown): p is GitLabDirectAccessResponse & { token: string } {
  return typeof p === "object" && p !== null && extractGitLabWorkflowToken(p as GitLabDirectAccessResponse) !== undefined;
}

Try / catch

try {
  await requestDirectAccess(...);
} catch (err) {
  if (err instanceof AIError.ProviderResponseError && err.message.includes("did not return credentials")) {
    throw new Error("Duo Workflow credentials missing — enable Duo Workflow for this namespace/project or check GitLab version");
  }
  throw err;
}

Prevention

When it happens

Trigger: direct_access responds ok but the body lacks any recognized token fields — Duo Workflow service not provisioned for the namespace, GitLab version returning a different response shape, or an empty object returned by a proxy.

Common situations: GitLab project/group without Duo Workflow activated; older self-managed GitLab whose direct_access response schema differs from what the client expects; intermediary (proxy/gateway) swallowing the body.

Related errors


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