ruvnet/ruflo · error · Error

Unauthorized

Error message

Unauthorized

What it means

After finding the task, task_update refuses to modify tasks whose status is 'completed' or 'cancelled' with 'Cannot update task with status: <status>' — priority/description/metadata changes and the like are frozen once a task reaches those terminal states. Notably 'failed' is NOT in this list, so failed tasks can still be updated (unlike in task_assign, where failed is terminal); only completion and cancellation lock the record.

Solutions

  1. Gate updates on live status: only call task_update when status is not 'completed'/'cancelled' (fetch task_status first)
  2. For finished tasks, record changes in your own store or a new linked task rather than mutating the frozen one
  3. Re-check status immediately before the update in racy flows — completion can land between check and write
  4. Catch this message and skip gracefully in batch annotators instead of aborting the whole batch

Example fix

// before
await client.callTool('task_update', { taskId, metadata: { report: 'v2' } }); // task completed -> throws [1138]

// after
const { status } = await client.callTool('task_status', { taskId });
if (status !== 'completed' && status !== 'cancelled') {
  await client.callTool('task_update', { taskId, metadata: { report: 'v2' } });
} else {
  console.log(`task ${taskId} is ${status}; recording report externally`);
}
Defensive patterns

Strategy: type-guard

Validate before calling

const { status } = await client.callTool('task_status', { taskId });
const frozen = status === 'completed' || status === 'cancelled';
if (frozen) { /* record change externally instead of task_update */ }

Type guard

function isUpdatableStatus(s: TaskStatus): s is 'pending' | 'queued' | 'assigned' | 'running' | 'failed' {
  return s !== 'completed' && s !== 'cancelled'; // note: failed IS updatable here, unlike assign
}

Try / catch

try {
  await client.callTool('task_update', { taskId, metadata });
} catch (e) {
  if (e instanceof Error && e.message.startsWith('Cannot update task with status')) {
    externalAnnotations.set(taskId, metadata); // frozen — keep the record on your side
  } else throw e;
}

Prevention

When it happens

Trigger: Changing priority or description of a task a worker just completed; merging metadata into a cancelled task; a UI edit racing task completion; cleanup scripts that annotate all tasks after a run, including finished ones.

Common situations: Post-run reporting that tries to stamp metadata onto every task; late-arriving updates from slow agents after the coordinator cancelled the task; retry tooling that updates instead of recreating.

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/95287ac31371e962. Report an issue: GitHub.

Appendix: source

Thrown at ruflo/src/ruvocal/src/lib/server/auth.ts:483

				const user = await collections.users.findOne({ hfUserId: cacheHit.userId });
				if (!user) {
					throw new Error("User not found");
				}
				return {
					user,
					sessionId,
					token,
					secretSessionId,
					isAdmin: user.isAdmin || adminTokenManager.isAdmin(sessionId),
				};
			}

			const response = await fetch("https://huggingface.co/api/whoami-v2", {
				headers: { Authorization: `Bearer ${token}` },
			});

			if (!response.ok) {
				throw new Error("Unauthorized");
			}

			const data = await response.json();
			const user = await collections.users.findOne({ hfUserId: data.id });
			if (!user) {
				throw new Error("User not found");
			}

			await collections.tokenCaches.insertOne({
				tokenHash: hash,
				userId: data.id,
				createdAt: new Date(),
				updatedAt: new Date(),
			});

			return {
				user,
				sessionId,

View on GitHub (pinned to fa13ee4ad6)