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
- Check the HTTP status in the message: 401/403 → re-issue the GitLab token with api scope and Duo Workflow entitlement
- The auth-retry path only fires when the status is embedded — if you catch this error, inspect errorStatus and retry with refreshed credentials
- For 429, back off and retry after the rate-limit window
- 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
- Keep the GitLab PAT fresh and scoped with api access
- Confirm Duo Workflow entitlement on your GitLab plan before integration
- Respect the embedded HTTP status: the message is designed to be machine-parsed for retry/rotation
- Handle 429 with real backoff rather than tight retries
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
- Bedrock HTTP ${response.status}: ${errBody.slice(0, 1000)}
- Devin auth error ${response.status} ${response.statusText}:
- gitlab-duo-agent
- GitLab Duo Workflow create failed with HTTP ${response.statu
- Failed to create Smithery auth session: ${response.status} $
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/63a49ad5ca82db9b.
Report an issue: GitHub.