{"record":{"id":"a654bfa872fe1743","repo":"odysseus-dev/odysseus","slug":"task-cannot-chain-to-itself","errorCode":null,"errorMessage":"Task cannot chain to itself","messagePattern":"Task cannot chain to itself","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"routes/task_routes.py","lineNumber":442,"sourceCode":"    # Actions that execute shell/SSH commands or cross into admin-only\n    # Cookbook serving surfaces — restricted to admins.\n    # Non-admin users cannot create tasks with these action types via the\n    # API. See review CRIT-C.\n    _ADMIN_ONLY_ACTIONS = ADMIN_ONLY_TASK_ACTIONS\n\n    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","sourceCodeStart":424,"sourceCodeEnd":460,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/task_routes.py#L424-L460","documentation":"Raised during task validation when a task's then_task_id (the task to chain/trigger after completion) equals the task's own id, producing an infinite self-trigger loop. Detected in _validate_then_task_id via current_task_id comparison and returned as HTTP 400.","triggerScenarios":"PUT /api/tasks/{id} with then_task_id set to the same id in the URL/body; or creating a task whose generated id is then edited to chain to itself; on create the current_task_id may be None so this fires mainly on updates.","commonSituations":"UI pre-populates the chain field with the task being edited; copy-paste of a task JSON that includes its own id; frontend bug that defaults then_task_id to the selected row in an edit form.","solutions":["Clear or change then_task_id so it points to a different task id.","Fix the edit form to exclude the current task from the chain-target picker.","If self-chaining (recurring re-trigger) is genuinely wanted, use a schedule/cron trigger instead of chaining."],"exampleFix":"// before\nform.then_task_id = task.id; // edit form prefilled with itself\n\n// after\nform.then_task_id = tasks.find(t => t.id !== task.id)?.id ?? null;","handlingStrategy":"validation","validationCode":"function validateChain(form, currentTaskId) {\n  const t = (form.then_task_id ?? '').trim();\n  if (!t) return null;\n  if (currentTaskId && t === currentTaskId)\n    throw new ValidationError('Task cannot chain to itself');\n  return t;\n}","typeGuard":null,"tryCatchPattern":"try { await api.updateTask(id, patch); }\ncatch (e) {\n  if (e.status === 400 && /chain to itself/.test(e.message)) { form.then_task_id = null; return; }\n  throw e;\n}","preventionTips":["Exclude the current task id from the chain-target dropdown in edit forms.","Trim then_task_id before submit (also avoids the duplicate-check mismatch).","Use cron/daily schedules for self-repeating behavior instead of self-chaining."],"tags":["tasks","validation","http-400","chaining"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}