{"record":{"id":"c8deeb0938371c6a","repo":"odysseus-dev/odysseus","slug":"chained-task-not-found","errorCode":null,"errorMessage":"Chained task not found","messagePattern":"Chained task not found","errorType":"http","errorClass":"HTTPException","httpStatus":404,"severity":"error","filePath":"routes/task_routes.py","lineNumber":448,"sourceCode":"    def _is_admin(user: str | None) -> bool:\n        return owner_has_admin_task_privileges(user)\n\n    def _require_admin_for_task_action(user: str | None, task_type: str | None, action: str | None) -> None:\n        if is_admin_only_task_action(task_type, action) and not _is_admin(user):\n            raise HTTPException(403, f\"Action '{action}' requires admin privileges\")\n\n    def _validate_then_task_id(db, then_task_id: Optional[str], user: Optional[str], current_task_id: Optional[str] = None) -> Optional[str]:\n        target_id = (then_task_id or \"\").strip()\n        if not target_id:\n            return None\n        if current_task_id and target_id == current_task_id:\n            raise HTTPException(400, \"Task cannot chain to itself\")\n        q = db.query(ScheduledTask).filter(ScheduledTask.id == target_id)\n        if user:\n            q = q.filter(ScheduledTask.owner == user)\n        target = q.first()\n        if not target:\n            raise HTTPException(404, \"Chained task not found\")\n        return target.id\n\n    @router.post(\"\")\n    async def create_task(request: Request, req: TaskCreate):\n        user = _owner(request)\n\n        # Validate\n        if req.task_type in (\"llm\", \"research\") and not req.prompt:\n            raise HTTPException(400, \"Prompt is required for LLM/research tasks\")\n        if req.task_type == \"action\" and not req.action:\n            raise HTTPException(400, \"Action name is required for action tasks\")\n        # Block shell-executing action types for non-admins. action_run_local\n        # uses subprocess.run(shell=True) and ssh_command / run_script run\n        # arbitrary commands.\n        _require_admin_for_task_action(user, req.task_type, req.action)\n        if req.trigger_type == \"schedule\" and not req.schedule:\n            raise HTTPException(400, \"Schedule is required for schedule-triggered tasks\")\n        if req.trigger_type == \"schedule\" and req.schedule == \"cron\" and not req.cron_expression:","sourceCodeStart":430,"sourceCodeEnd":466,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/task_routes.py#L430-L466","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Verify the target id exists: GET /api/tasks/{then_task_id} must return 200 before submitting the chain.","If it returns 404/403, re-create the target task or pick a task owned by the same user.","Create the target task first, capture its id from the response, then set then_task_id on the parent."],"exampleFix":"# before\ntask = client.create_task({...})\nclient.create_task({..., \"then_task_id\": guessed_id})\n\n# after\nchild = client.create_task({...})\nassert child[\"id\"], \"child task must exist\"\nparent = client.create_task({..., \"then_task_id\": child[\"id\"]})","handlingStrategy":"validation","validationCode":"async function resolveChainTarget(thenTaskId, owner) {\n  if (!thenTaskId) return null;\n  const res = await fetch(`/api/tasks/${encodeURIComponent(thenTaskId.trim())}`);\n  if (res.status === 404) throw new ValidationError('Chained task not found');\n  if (res.status === 403) throw new ValidationError('Chained task belongs to another user');\n  return thenTaskId.trim();\n}","typeGuard":null,"tryCatchPattern":"try { await api.createTask(payload); }\ncatch (e) {\n  if (e.status === 404 && /Chained task/.test(e.message)) { refreshTaskList(); return; }\n  throw e;\n}","preventionTips":["Populate chain targets from the live owner-scoped task list, not stored ids.","Create the child task first and use the id from its response.","Handle task-deleted events by pruning stale then_task_id references in local state."],"tags":["tasks","http-404","chaining","validation"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}