{"record":{"id":"d65f6b81f32bbd49","repo":"openai/codex-plugin-cc","slug":"package-json-version-must-be-a-string","errorCode":null,"errorMessage":"package.json version must be a string.","messagePattern":"package\\.json version must be a string\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/bump-version.mjs","lineNumber":153,"sourceCode":"  const plugin = json.plugins?.find((entry) => entry?.name === \"codex\");\n  requireObject(plugin, \".claude-plugin/marketplace.json plugins[codex]\");\n  return plugin;\n}\n\nfunction readJson(root, file) {\n  const filePath = path.join(root, file);\n  return JSON.parse(fs.readFileSync(filePath, \"utf8\"));\n}\n\nfunction writeJson(root, file, json) {\n  const filePath = path.join(root, file);\n  fs.writeFileSync(filePath, `${JSON.stringify(json, null, 2)}\\n`);\n}\n\nfunction readPackageVersion(root) {\n  const packageJson = readJson(root, \"package.json\");\n  if (typeof packageJson.version !== \"string\") {\n    throw new Error(\"package.json version must be a string.\");\n  }\n  validateVersion(packageJson.version);\n  return packageJson.version;\n}\n\nfunction checkVersions(root, expectedVersion) {\n  const mismatches = [];\n\n  for (const target of TARGETS) {\n    const json = readJson(root, target.file);\n    for (const value of target.values) {\n      const actual = value.get(json);\n      if (actual !== expectedVersion) {\n        mismatches.push(`${target.file} ${value.label}: expected ${expectedVersion}, found ${actual ?? \"<missing>\"}`);\n      }\n    }\n  }\n","sourceCodeStart":135,"sourceCodeEnd":171,"githubUrl":"https://github.com/openai/codex-plugin-cc/blob/db52e28f4d9ded852ab3942cea316258ae4ef346/scripts/bump-version.mjs#L135-L171","documentation":"Thrown by readPackageVersion() when package.json's `version` field exists but is not a string (e.g. a number, null, object). It is a type guard run before validateVersion, because the regex test would coerce/throw on non-strings. Used by the --check path that infers the expected version from package.json.","triggerScenarios":"Running `node scripts/bump-version.mjs --check` (no version) on a repo whose package.json has `\"version\": 1.0.0` (numeric) or `\"version\": null` or no version field at all (typeof undefined !== 'string').","commonSituations":"package.json hand-edited with an unquoted numeric version; version field deleted; a JSON5/JSON-with-comments artifact; tooling that wrote a non-string version.","solutions":["Edit package.json so `version` is a quoted semver string: \"version\": \"1.0.0\".","If the field is missing, add it under the top-level object.","Re-validate the JSON with `node -e \"JSON.parse(require('fs').readFileSync('package.json','utf8'))\"` to catch corruption."],"exampleFix":"// before — package.json\n{ \"name\": \"codex\", \"version\": 1.0 }\n\n// after\n{ \"name\": \"codex\", \"version\": \"1.0.0\" }","handlingStrategy":"type-guard","validationCode":"function readSafePackageVersion(root) {\n  const pkg = JSON.parse(fs.readFileSync(path.join(root, \"package.json\"), \"utf8\"));\n  if (typeof pkg.version !== \"string\") {\n    throw new Error(\"package.json version missing or not a string.\");\n  }\n  return pkg.version;\n}","typeGuard":"function hasStringVersion(pkg) {\n  return pkg != null && typeof pkg === \"object\" && typeof pkg.version === \"string\";\n}","tryCatchPattern":"try {\n  readPackageVersion(root);\n} catch (err) {\n  if (/version must be a string/.test(err.message)) {\n    // prompt user to fix package.json before retrying\n  } else throw err;\n}","preventionTips":["Keep package.json's version as a quoted semver string.","Lint manifests (e.g. npm pkg fix or a JSON schema check) before release.","Never hand-edit numeric values into JSON without quotes."],"tags":["validation","json","manifest","bump-version","types"],"backgroundTag":null,"analyzedSha":"db52e28f4d9ded852ab3942cea316258ae4ef346","analyzedAt":"2026-08-13T05:13:20.855Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}