eyaltoledano/claude-task-master · error · AuthenticationError

FLOW_NOT_FOUND

FLOW_NOT_FOUND

Error message

Authentication flow expired or not found

What it means

AuthenticationError thrown during polling when the auth server responds with HTTP 404 for the flow-status endpoint, meaning the authentication flow ID is unknown or has expired server-side. The library treats this as terminal — polling will not succeed by continuing, so it aborts immediately.

Source

Thrown at packages/tm-core/src/modules/auth/services/oauth-service.ts:339

			);
		}

		while (Date.now() - startTime < timeout) {
			try {
				const response = await fetch(statusUrl, {
					method: 'GET',
					headers: {
						'User-Agent': `TaskMasterCLI/${this.getCliVersion()}`
					}
				});

				if (!response.ok) {
					const errorData = (await response.json().catch(() => ({}))) as {
						message?: string;
					};

					if (response.status === 404) {
						throw new AuthenticationError(
							'Authentication flow expired or not found',
							'FLOW_NOT_FOUND'
						);
					}

					throw new AuthenticationError(
						errorData.message || `HTTP ${response.status}`,
						'POLL_FAILED'
					);
				}

				const data = (await response.json()) as FlowStatusResponse;

				if (!data.success) {
					throw new AuthenticationError(
						data.message || 'Failed to check status',
						'POLL_FAILED'
					);

View on GitHub (pinned to c0c98d367c)

Solutions

  1. Restart the entire authentication flow (start a new PKCE flow) — the old flowId cannot be recovered.
  2. Complete the browser authorization step promptly, within the server's flow TTL.
  3. If self-hosting behind multiple instances, enable shared/sticky storage for CLI auth flows.
  4. Verify baseUrl points to the same environment that issued the flowId (dev vs prod mismatch).
  5. Check server logs for flow_id to confirm whether it expired or was never created.

Example fix

// before: retrying a dead flow
await oauth.pollForCompletion(oldFlowId, 60000); // 404 -> FLOW_NOT_FOUND
// after: catch and restart the flow
try {
  await oauth.pollForCompletion(flowId, 60000);
} catch (e) {
  if (e.code === 'FLOW_NOT_FOUND') {
    const flow = await oauth.startBackendFlow();
    await oauth.pollForCompletion(flow.flowId, 60000);
  }
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Cannot be predicted client-side; mitigate by completing authorization promptly.
// Optionally verify the flow is fresh before polling:
if (Date.now() - flow.startedAt > 5 * 60 * 1000) {
  flow = await oauth.startBackendFlow(); // flow likely expired server-side
}

Type guard

function isFlowNotFound(e: unknown): e is AuthenticationError {
  return e instanceof AuthenticationError && (e as any).code === 'FLOW_NOT_FOUND';
}

Try / catch

try {
  await oauth.pollForCompletion(flowId, timeout);
} catch (e) {
  if (isFlowNotFound(e)) {
    // Flow expired or server restarted — start a brand-new login flow
    const fresh = await oauth.startBackendFlow();
    await oauth.pollForCompletion(fresh.flowId, timeout);
  }
}

Prevention

When it happens

Trigger: pollForCompletion receives a 404 from /api/auth/cli/status?flow_id=... because the flow timed out on the server, the server was restarted (in-memory flow store), a load balancer routed the poll to a different instance, or the flowId is wrong/already consumed.

Common situations: User taking longer than the server-side flow TTL to authorize in the browser; multi-instance backend without shared session storage; re-running a poll after a completed/expired login; pointing at a different environment's auth server than the one that issued the flowId.

Understand the failure class

Related errors


AI-assisted analysis of eyaltoledano/claude-task-master@c0c98d367c (2026-08-29). Data as JSON: /api/errors/5139bdb4197bf440. Report an issue: GitHub.