odysseus-dev/odysseus · error · HTTPException
Cannot chain to another user's task
Error message
Cannot chain to another user's task
What it means
Ownership guard on task chaining: the chained target task exists but belongs to a different owner than the requesting user, so linking would let one user trigger another's tasks. Returned as HTTP 403.
Source
Thrown at routes/task_routes.py:527
task_id = str(uuid.uuid4())
db = SessionLocal()
try:
then_task_id = _validate_then_task_id(db, req.then_task_id, user)
notifications_enabled = (
False if req.task_type == "action" and req.notifications_enabled is None
else bool(req.notifications_enabled) if req.notifications_enabled is not None
else True
)
# Validate chained task belongs to same owner
if req.then_task_id:
chain_target = db.query(ScheduledTask).filter(
ScheduledTask.id == req.then_task_id
).first()
if not chain_target:
raise HTTPException(400, "Chained task not found")
if chain_target.owner != user:
raise HTTPException(403, "Cannot chain to another user's task")
task = ScheduledTask(
id=task_id,
owner=user,
name=name,
prompt=req.prompt,
task_type=req.task_type,
action=req.action,
schedule=req.schedule,
scheduled_time=req.scheduled_time,
scheduled_day=req.scheduled_day,
scheduled_date=sched_date,
cron_expression=req.cron_expression,
trigger_type=req.trigger_type,
trigger_event=req.trigger_event,
trigger_count=req.trigger_count,
trigger_counter=0,
next_run=next_run,
status="active" if (req.trigger_type in ("event", "webhook") or next_run) else "completed",View on GitHub (pinned to f9235ebbf1)
Solutions
- Chain only to tasks owned by the same account.
- Restrict the client's chain-target picker to the current user's task list (GET /api/tasks already scopes by owner).
- If cross-user chaining is a legitimate feature request, it needs an explicit server-side grant mechanism, not removal of this check.
Example fix
// before
const target = allTasks.find(t => t.name === 'Admin Cleanup'); // may be another user's
// after
const myTasks = await api.listTasks(); // owner-scoped
const target = myTasks.find(t => t.name === 'Cleanup');
if (!target) throw new Error('Pick one of your own tasks'); Defensive patterns
Strategy: validation
Validate before calling
async function canChainTo(thenTaskId) {
const res = await fetch(`/api/tasks/${encodeURIComponent(thenTaskId)}`);
if (res.status === 403) return false; // not yours
return res.ok;
} Try / catch
try { await api.createTask(p); }
catch (e) {
if (e.status === 403 && /another user/.test(e.message)) { showOwnTasksOnly(); return; }
throw e;
} Prevention
- Source chain targets exclusively from the owner-scoped task list endpoint.
- Never accept task ids typed manually by users for chaining.
- Test cross-owner chaining with two accounts in CI to confirm 403.
When it happens
Trigger: POST/PUT a task with then_task_id equal to an id owned by another account on a multi-owner instance. The earlier helper only applies the owner filter when user is set, so this explicit owner != user comparison is the authoritative cross-owner block.
Common situations: Multi-user deployment where a user discovers/guesses another's task id; admin-created tasks being chained by regular users; shared ids in documentation or exported configs; migrating tasks between accounts leaving stale chain references.
Related errors
- Access denied
- Action '{action}' requires admin privileges
- Failed to update task
- Admin only
- API token missing required scope: {required}
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/1daca2d0aed9fbbe.
Report an issue: GitHub.