{"record":{"id":"3b24ff03b63aacfa","repo":"eyaltoledano/claude-task-master","slug":"invalid-subtask-id-subid-subtask-id-must-be-a","errorCode":null,"errorMessage":"Invalid subtask ID: ${subId}. Subtask ID must be a positive integer.","messagePattern":"Invalid subtask ID: (.+?)\\. Subtask ID must be a positive integer\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/tm-core/src/modules/storage/adapters/file-storage/file-storage.ts","lineNumber":516,"sourceCode":"\t */\n\tprivate async updateSubtaskStatusInFile(\n\t\ttasks: Task[],\n\t\tsubtaskId: string,\n\t\tnewStatus: TaskStatus,\n\t\ttag?: string\n\t): Promise<UpdateStatusResult> {\n\t\t// Parse the subtask ID to get parent ID and subtask ID\n\t\tconst parts = subtaskId.split('.');\n\t\tif (parts.length !== 2) {\n\t\t\tthrow new Error(\n\t\t\t\t`Invalid subtask ID format: ${subtaskId}. Expected format: parentId.subtaskId`\n\t\t\t);\n\t\t}\n\n\t\tconst [parentId, subIdRaw] = parts;\n\t\tconst subId = subIdRaw.trim();\n\t\tif (!/^\\d+$/.test(subId)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Invalid subtask ID: ${subId}. Subtask ID must be a positive integer.`\n\t\t\t);\n\t\t}\n\t\tconst subtaskNumericId = Number(subId);\n\n\t\t// Find the parent task\n\t\tconst parentTaskIndex = tasks.findIndex(\n\t\t\t(t) => String(t.id) === String(parentId)\n\t\t);\n\n\t\tif (parentTaskIndex === -1) {\n\t\t\tthrow new Error(`Parent task ${parentId} not found`);\n\t\t}\n\n\t\tconst parentTask = tasks[parentTaskIndex];\n\n\t\t// Find the subtask within the parent task\n\t\tconst subtaskIndex = parentTask.subtasks.findIndex(","sourceCodeStart":498,"sourceCodeEnd":534,"githubUrl":"https://github.com/eyaltoledano/claude-task-master/blob/c0c98d367c55296bfe69e65680625b6db437af02/packages/tm-core/src/modules/storage/adapters/file-storage/file-storage.ts#L498-L534","documentation":"After splitting a dotted subtask ID, updateSubtaskStatusInFile() validates that the subtask segment is a positive integer (digits only). If the portion after the dot is non-numeric (e.g. '5.a' or '5.2x'), it throws this error so downstream numeric comparisons against subtask ids are safe.","triggerScenarios":"Passing an ID like '5.abc', '5.2x', or '5.-1' where the subtask segment is not purely digits; usually the result of unvalidated user input or mixing ID schemes from another system.","commonSituations":"CLI users typing descriptive text instead of the numeric subtask index, importing IDs from external trackers with alphanumeric keys, or string concatenation bugs building the subtask ID dynamically.","solutions":["Sanitize the subtask segment to digits-only before calling (e.g. parseInt and re-validate)","Use /^\\d+\\.\\d+$/ to validate the full dotted ID up front","Confirm the subtask's numeric index from the parent task's subtasks list","Reject/reprompt on invalid user input before invoking the storage API"],"exampleFix":"// before\nawait storage.updateTaskStatus(`5.${userInput}`, 'done'); // 'abc' throws\n// after\nconst subId = Number.parseInt(userInput, 10);\nif (!Number.isInteger(subId) || subId <= 0) {\n  throw new Error(`Subtask index must be a positive integer, got: ${userInput}`);\n}\nawait storage.updateTaskStatus(`5.${subId}`, 'done');","handlingStrategy":"validation","validationCode":"function isPositiveIntSegment(seg: string): boolean {\n  return /^\\d+$/.test(seg.trim());\n}\nfunction canUpdateSubtask(id: string): boolean {\n  const [parent, sub] = id.split('.');\n  return id.split('.').length === 2 && /^\\d+$/.test(parent) && isPositiveIntSegment(sub);\n}\n// guard: if (!canUpdateSubtask(id)) reject();","typeGuard":"function isInvalidSubtaskIdError(e: unknown): e is Error {\n  return e instanceof Error && e.message.startsWith('Invalid subtask ID:');\n}","tryCatchPattern":"try {\n  await storage.updateTaskStatus(subtaskId, newStatus);\n} catch (e) {\n  if (isInvalidSubtaskIdError(e)) {\n    // coerce/re-prompt: const n = parseInt(sub, 10); if (!Number.isInteger(n) || n <= 0) ...\n  } else throw e;\n}","preventionTips":["Coerce the subtask segment with parseInt and reject NaN/<=0 before calling","Reject alphanumeric external tracker keys at the input boundary","Validate the whole dotted ID with /^\\d+\\.\\d+$/ before any storage call","When building IDs via concatenation, assert numeric segments first"],"tags":["validation","subtask","id-format"],"backgroundTag":"invalid-id-format","analyzedSha":"c0c98d367c55296bfe69e65680625b6db437af02","analyzedAt":"2026-08-29T02:56:26.071Z","schemaVersion":2},"datasetVersion":"2026-08-29T07:17:48.351Z"}