ruvnet/ruflo · error · Error

User token not found

Error message

User token not found

What it means

In task_assign's simple implementation, after the task is found and the already-assigned soft-return is skipped, the handler refuses to assign tasks whose status is terminal: 'completed', 'cancelled', or 'failed' all throw 'Cannot assign task with status: <status>'. This is a lifecycle guard — dead tasks cannot be handed to an agent. Note the asymmetry: 'pending', 'queued', 'assigned', and 'running' are all assignable, and 'failed' is terminal here but still updatable in task_update.

Solutions

  1. Check task_status immediately before assigning and only proceed for pending/queued/assigned/running
  2. If the task failed and needs rework, create a new task carrying over metadata instead of resurrecting the terminal one
  3. Catch this error specifically and treat it as a signal to refresh your local task view
  4. Avoid fire-and-forget assignment races: mark intent in your orchestrator before calling task_assign

Example fix

// before
await client.callTool('task_assign', { taskId, agentId: 'worker-2' }); // task.status === 'failed' -> throws [1136]

// after
const { status } = await client.callTool('task_status', { taskId });
if (!['pending', 'queued', 'assigned', 'running'].includes(status)) {
  const { task } = await client.callTool('task_create', { type: task.type, description: `retry of ${taskId}` });
  await client.callTool('task_assign', { taskId: task.id, agentId: 'worker-2' });
} else {
  await client.callTool('task_assign', { taskId, agentId: 'worker-2', reassign: true });
}
Defensive patterns

Strategy: type-guard

Validate before calling

const ASSIGNABLE = new Set(['pending', 'queued', 'assigned', 'running']);
const { status } = await client.callTool('task_status', { taskId });
if (!ASSIGNABLE.has(status)) throw new Error(`task ${taskId} is ${status}; create a new task instead`);

Type guard

type TaskStatus = 'pending' | 'queued' | 'assigned' | 'running' | 'completed' | 'failed' | 'cancelled';
function isAssignableStatus(s: TaskStatus): s is 'pending' | 'queued' | 'assigned' | 'running' {
  return s === 'pending' || s === 'queued' || s === 'assigned' || s === 'running';
}

Try / catch

try {
  await client.callTool('task_assign', { taskId, agentId });
} catch (e) {
  if (e instanceof Error && e.message.startsWith('Cannot assign task with status')) {
    // terminal task — create a successor task instead of retrying
  } else throw e;
}

Prevention

When it happens

Trigger: Assigning a task that a worker already completed between your status check and the assign call; retrying assignment of a failed task without resetting it; re-dispatching cancelled tasks during a re-run; racing a second agent against a task that just finished.

Common situations: Retry logic that blindly re-assigns after timeouts when the task actually failed; UIs that show stale 'pending' state and let users assign; pipelines that do not branch on terminal status before dispatch.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18). Data as JSON: /api/errors/7f073d76a0937f54. Report an issue: GitHub.

Appendix: source

Thrown at ruflo/src/ruvocal/src/lib/server/apiToken.ts:6

import { config } from "$lib/server/config";

export function getApiToken(locals: App.Locals | undefined) {
	if (config.USE_USER_TOKEN === "true") {
		if (!locals?.token) {
			throw new Error("User token not found");
		}
		return locals.token;
	}
	return config.OPENAI_API_KEY || config.HF_TOKEN;
}

View on GitHub (pinned to fa13ee4ad6)