{"record":{"id":"0de6a8ef5e8ed61f","repo":"vxcontrol/pentagi","slug":"invalid-subtask-patch-w-0de6a8","errorCode":null,"errorMessage":"invalid subtask patch: %w","messagePattern":"invalid subtask patch: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/pkg/tools/flow_manager.go","lineNumber":920,"sourceCode":"\t\t\t\t\t\t\"Call %s first, then retry\",\n\t\t\t\t\taction.TaskID, st.ID, st.Title, StopFlowToolName))\n\t\t\t}\n\t\t}\n\n\t\treturn \"\", stateGuard(fmt.Errorf(\n\t\t\t\"no 'created' subtasks found for task %d; \"+\n\t\t\t\t\"all subtasks have been executed or the task has no plan yet. \"+\n\t\t\t\t\"Use %s to create a new task instead\",\n\t\t\taction.TaskID, SubmitFlowInputToolName))\n\t}\n\n\tpatch := SubtaskPatch{\n\t\tOperations: action.Operations,\n\t\tMessage:    action.Message,\n\t}\n\n\tif err := patch.Validate(); err != nil {\n\t\treturn \"\", fmt.Errorf(\"invalid subtask patch: %w\", err)\n\t}\n\n\tif len(action.Operations) == 0 {\n\t\treturn fmt.Sprintf(\"No operations provided — the subtask plan for task %d is unchanged.\", action.TaskID), nil\n\t}\n\n\tif err := t.handler(ctx, action.TaskID, patch); err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to patch subtasks for task %d: %w\", action.TaskID, err)\n\t}\n\n\t// Query the new subtask list so the LLM can correlate the patched entries with their new IDs.\n\tnewPlanned, err := t.db.GetTaskPlannedSubtasks(ctx, action.TaskID)\n\tif err != nil {\n\t\t// Not fatal — operations were applied; just warn and skip the list.\n\t\treturn fmt.Sprintf(\n\t\t\t\"%d operation(s) applied to the subtask plan for task %d. \"+\n\t\t\t\t\"Could not retrieve updated subtask list: %s. \"+\n\t\t\t\t\"Call %s with detail='planned' and task_id=%d to verify.\",","sourceCodeStart":902,"sourceCodeEnd":938,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/tools/flow_manager.go#L902-L938","documentation":"The patch_flow_subtasks tool handler builds a SubtaskPatch from the LLM-provided operations and runs SubtaskPatch.Validate() before applying anything. Validation enforces per-operation-type invariants: add requires title and description; remove/modify/reorder require an id; modify requires at least title or description; and op must be one of add/remove/modify/reorder. Any violation aborts the whole patch with \"invalid subtask patch: %w\".","triggerScenarios":"Calling patch_flow_subtasks where operations[i] has: an unknown op value (typo like \"update\" or \"delete\" instead of \"modify\"/\"remove\"); an add without title or without description; a remove/modify/reorder missing the id field; or a modify that sets neither title nor description. The failing operation index and reason are embedded in the wrapped message.","commonSituations":"An LLM emits \"op\": \"delete\" or \"move\" instead of the enum names; the model omits id because it assumed positional indexing; add operations produced with empty description because the model only wrote a title; hand-crafted JSON in tests that predates the Validate() rules.","solutions":["Read the wrapped cause (e.g. \"operation 2: modify requires at least title or description\") and fix that specific operation in the tool call arguments.","Use only the enum values add, remove, modify, reorder for the op field.","For add, supply both title and description; for remove/modify/reorder, always include the numeric subtask id copied from get_flow_status output.","If no changes are needed, send an empty operations array (handled gracefully) instead of a malformed placeholder operation."],"exampleFix":"// before\n{\"task_id\": 3, \"operations\": [{\"op\": \"update\", \"title\": \"Scan web app\"}]}\n// after\n{\"task_id\": 3, \"operations\": [{\"op\": \"add\", \"title\": \"Scan web app\", \"description\": \"Enumerate and scan the web application for vulnerabilities\"}]}","handlingStrategy":"validation","validationCode":"validOps := map[string]bool{\"add\": true, \"remove\": true, \"modify\": true, \"reorder\": true}\nfor i, op := range operations {\n    if !validOps[string(op.Op)] {\n        return fmt.Errorf(\"operation %d: unknown op %q\", i, op.Op)\n    }\n    if op.Op == \"add\" && (op.Title == \"\" || op.Description == \"\") {\n        return fmt.Errorf(\"operation %d: add requires title and description\", i)\n    }\n    if (op.Op == \"remove\" || op.Op == \"modify\" || op.Op == \"reorder\") && op.ID == nil {\n        return fmt.Errorf(\"operation %d: %s requires id\", i, op.Op)\n    }\n    if op.Op == \"modify\" && op.Title == \"\" && op.Description == \"\" {\n        return fmt.Errorf(\"operation %d: modify requires title or description\", i)\n    }\n}","typeGuard":null,"tryCatchPattern":"if _, err := toolHandle(ctx, action); err != nil {\n    var invalid *fmt.WrapError // or: strings.Contains(err.Error(), \"invalid subtask patch\")\n    if strings.HasPrefix(err.Error(), \"invalid subtask patch:\") {\n        // do NOT retry as-is; log err and regenerate a schema-conformant patch\n    }\n}","preventionTips":["Copy op enum values verbatim from the tool schema: add, remove, modify, reorder.","Always pair add with both title and description.","Always include the numeric id for remove/modify/reorder, taken from a fresh get_flow_status read.","Send an empty operations array when nothing needs to change instead of a dummy operation."],"tags":["validation","go","tool-arguments","llm"],"backgroundTag":"tool-argument-validation-failed","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}