odysseus-dev/odysseus · error · HTTPException

Chained task not found

Error message

Chained task not found

What it means

Raised by _validate_then_task_id when then_task_id names a task that does not exist in the ScheduledTask table (optionally additionally filtered by owner). The chained target must already exist before it can be referenced. Returned as HTTP 404.

Source

Thrown at routes/task_routes.py:448

    def _is_admin(user: str | None) -> bool:
        return owner_has_admin_task_privileges(user)

    def _require_admin_for_task_action(user: str | None, task_type: str | None, action: str | None) -> None:
        if is_admin_only_task_action(task_type, action) and not _is_admin(user):
            raise HTTPException(403, f"Action '{action}' requires admin privileges")

    def _validate_then_task_id(db, then_task_id: Optional[str], user: Optional[str], current_task_id: Optional[str] = None) -> Optional[str]:
        target_id = (then_task_id or "").strip()
        if not target_id:
            return None
        if current_task_id and target_id == current_task_id:
            raise HTTPException(400, "Task cannot chain to itself")
        q = db.query(ScheduledTask).filter(ScheduledTask.id == target_id)
        if user:
            q = q.filter(ScheduledTask.owner == user)
        target = q.first()
        if not target:
            raise HTTPException(404, "Chained task not found")
        return target.id

    @router.post("")
    async def create_task(request: Request, req: TaskCreate):
        user = _owner(request)

        # Validate
        if req.task_type in ("llm", "research") and not req.prompt:
            raise HTTPException(400, "Prompt is required for LLM/research tasks")
        if req.task_type == "action" and not req.action:
            raise HTTPException(400, "Action name is required for action tasks")
        # Block shell-executing action types for non-admins. action_run_local
        # uses subprocess.run(shell=True) and ssh_command / run_script run
        # arbitrary commands.
        _require_admin_for_task_action(user, req.task_type, req.action)
        if req.trigger_type == "schedule" and not req.schedule:
            raise HTTPException(400, "Schedule is required for schedule-triggered tasks")
        if req.trigger_type == "schedule" and req.schedule == "cron" and not req.cron_expression:

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Verify the target id exists: GET /api/tasks/{then_task_id} must return 200 before submitting the chain.
  2. If it returns 404/403, re-create the target task or pick a task owned by the same user.
  3. Create the target task first, capture its id from the response, then set then_task_id on the parent.

Example fix

# before
task = client.create_task({...})
client.create_task({..., "then_task_id": guessed_id})

# after
child = client.create_task({...})
assert child["id"], "child task must exist"
parent = client.create_task({..., "then_task_id": child["id"]})
Defensive patterns

Strategy: validation

Validate before calling

async function resolveChainTarget(thenTaskId, owner) {
  if (!thenTaskId) return null;
  const res = await fetch(`/api/tasks/${encodeURIComponent(thenTaskId.trim())}`);
  if (res.status === 404) throw new ValidationError('Chained task not found');
  if (res.status === 403) throw new ValidationError('Chained task belongs to another user');
  return thenTaskId.trim();
}

Try / catch

try { await api.createTask(payload); }
catch (e) {
  if (e.status === 404 && /Chained task/.test(e.message)) { refreshTaskList(); return; }
  throw e;
}

Prevention

When it happens

Trigger: POST/PUT a task with then_task_id set to an id that was deleted, belongs to another owner (when the owner filter applies), or is a typo/truncated paste (leading/trailing spaces are stripped, so mismatch must be in the id itself).

Common situations: Chaining to a task deleted earlier; two browser tabs where the target task was removed; copying task ids between instances/environments where ids differ; race where the target task is created asynchronously and referenced before commit.

Related errors


AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14). Data as JSON: /api/errors/c8deeb0938371c6a. Report an issue: GitHub.