{"record":{"id":"d40bf7eabaacd74f","repo":"affaan-m/ECC","slug":"invalid-target-version-targetversion","errorCode":null,"errorMessage":"Invalid target version: ${targetVersion}","messagePattern":"Invalid target version: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/lib/skill-evolution/versioning.js","lineNumber":188,"sourceCode":"    event: 'snapshot',\n    version: nextVersion,\n    reason: options.reason || null,\n    author: options.author || null,\n    status: 'applied',\n    created_at: createdAt,\n  });\n\n  return {\n    version: nextVersion,\n    path: snapshotPath,\n    created_at: createdAt,\n  };\n}\n\nfunction rollbackTo(skillPath, targetVersion, options = {}) {\n  const normalizedTargetVersion = Number(targetVersion);\n  if (!Number.isInteger(normalizedTargetVersion) || normalizedTargetVersion <= 0) {\n    throw new Error(`Invalid target version: ${targetVersion}`);\n  }\n\n  ensureSkillExists(skillPath);\n  ensureSkillVersioning(skillPath);\n\n  const targetPath = path.join(getVersionsDir(skillPath), `v${normalizedTargetVersion}.md`);\n  if (!fs.existsSync(targetPath)) {\n    throw new Error(`Version not found: v${normalizedTargetVersion}`);\n  }\n\n  const currentVersion = getCurrentVersion(skillPath);\n  const targetContent = fs.readFileSync(targetPath, 'utf8');\n  fs.writeFileSync(getSkillFilePath(skillPath), targetContent, 'utf8');\n\n  const createdVersion = createVersion(skillPath, {\n    timestamp: options.timestamp,\n    reason: options.reason || `rollback to v${normalizedTargetVersion}`,\n    author: options.author || null,","sourceCodeStart":170,"sourceCodeEnd":206,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/scripts/lib/skill-evolution/versioning.js#L170-L206","documentation":"Thrown by rollbackTo() when targetVersion cannot be coerced to a positive integer. The function calls Number(targetVersion) and then rejects anything that is not an integer or is <= 0, because version snapshots are stored as vN.md files with N >= 1. A non-numeric or zero/negative value would either produce a bogus filename or roll back to nothing.","triggerScenarios":"Calling rollbackTo(skillPath, 'latest'), rollbackTo(skillPath, 0), rollbackTo(skillPath, -1), rollbackTo(skillPath, 3.5), rollbackTo(skillPath, NaN), rollbackTo(skillPath, undefined), or rollbackTo(skillPath, '') — all fail the Number.isInteger check or the <= 0 guard.","commonSituations":"CLI accepts a user-supplied --to flag without validating it; reading a target version from a config that may be missing; off-by-one where the caller asks for v0 thinking versions are zero-indexed; passing the version label 'v3' instead of the number 3.","solutions":["Pass a positive integer (or numeric string of a positive integer) such as 2 or '2'.","Strip a leading 'v' if your input uses the label format: targetVersion.replace(/^v/i, '').","Use getCurrentVersion(skillPath) to discover the highest existing version, then pick a target strictly less than or equal to it.","Validate user input upstream with a regex like /^[1-9][0-9]*$/ before calling rollbackTo."],"exampleFix":"// before\nrollbackTo(skillPath, 'v2');   // 'v2' -> Number('v2') = NaN -> Invalid target version\n\n// after\nconst numericTarget = parseInt(String(targetVersion).replace(/^v/i, ''), 10);\nrollbackTo(skillPath, numericTarget);","handlingStrategy":"validation","validationCode":"function parseTargetVersion(raw) {\n  if (typeof raw === 'string') raw = raw.replace(/^v/i, '');\n  const n = Number(raw);\n  if (!Number.isInteger(n) || n <= 0) return null;\n  return n;\n}\n\nconst target = parseTargetVersion(input);\nif (target === null) throw new Error(`Bad target version: ${input}`);\nrollbackTo(skillPath, target);","typeGuard":"function isPositiveVersion(value) {\n  if (typeof value === 'string') value = value.replace(/^v/i, '');\n  const n = Number(value);\n  return Number.isInteger(n) && n > 0;\n}","tryCatchPattern":"try {\n  rollbackTo(skillPath, targetVersion);\n} catch (error) {\n  if (/Invalid target version/.test(error.message)) {\n    // prompt the user or fall back to getCurrentVersion\n    rollbackTo(skillPath, getCurrentVersion(skillPath));\n    return;\n  }\n  throw error;\n}","preventionTips":["Strip a leading 'v' from user input before parsing.","Validate with /^[1-9][0-9]*$/ at the CLI boundary.","Bound the target by getCurrentVersion(skillPath) — rollback targets must be <= the highest existing version.","Treat zero/negative/NaN as a config error, not a sentinel."],"tags":["skill-evolution","rollback","validation","numeric"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}