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
- Gate updates on live status: only call task_update when status is not 'completed'/'cancelled' (fetch task_status first)
- For finished tasks, record changes in your own store or a new linked task rather than mutating the frozen one
- Re-check status immediately before the update in racy flows — completion can land between check and write
- 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
- Stamp metadata while tasks are live, before completion races freeze them
- In batch annotators, skip (rather than abort on) completed/cancelled tasks
- Remember failed tasks remain updatable — only completed/cancelled are frozen
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.
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- User token not found
- Connection pool is shutting down
- Failed to create share link
- HTTP transport already running
- JSON.stringify(response.error)
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)