{"record":{"id":"0db3143cd7cd2abc","repo":"stablyai/orca","slug":"bin-orca-target-must-start-with-a-node-shebang","errorCode":null,"errorMessage":"bin.orca target must start with a Node shebang: ${binTarget}","messagePattern":"bin\\.orca target must start with a Node shebang: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"config/scripts/verify-cli-bin.mjs","lineNumber":46,"sourceCode":"  const packageJsonPath = path.join(projectDir, 'package.json')\n  const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'))\n  const binTarget = packageJson.bin?.orca\n  if (typeof binTarget !== 'string' || binTarget.length === 0) {\n    throw new Error('package.json must declare bin.orca')\n  }\n\n  const binPath = path.resolve(projectDir, binTarget)\n  const stats = statSync(binPath)\n  if (!stats.isFile()) {\n    throw new Error(`bin.orca target is not a file: ${binTarget}`)\n  }\n  if (stats.size === 0) {\n    throw new Error(`bin.orca target is empty: ${binTarget}`)\n  }\n\n  const content = readFileSync(binPath, 'utf8')\n  if (!content.startsWith('#!/usr/bin/env node\\n')) {\n    throw new Error(`bin.orca target must start with a Node shebang: ${binTarget}`)\n  }\n\n  const outPackageJsonPath = path.join(projectDir, 'out', 'package.json')\n  if (fixPackageJson) {\n    mkdirSync(path.dirname(outPackageJsonPath), { recursive: true })\n    writeFileSync(outPackageJsonPath, OUT_COMMONJS_PACKAGE_JSON, 'utf8')\n  }\n  let outPackageJson\n  try {\n    outPackageJson = JSON.parse(readFileSync(outPackageJsonPath, 'utf8'))\n  } catch (error) {\n    if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {\n      throw new Error(\n        `compiled CLI package boundary is missing: ${path.relative(projectDir, outPackageJsonPath)}`\n      )\n    }\n    throw error\n  }","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/config/scripts/verify-cli-bin.mjs#L28-L64","documentation":"The bin file must begin with the exact shebang `#!/usr/bin/env node\\n` so the OS launches it under Node when exec'd directly (as it will be from PATH). readFileSync reads the content and checks the prefix; any other shebang, a BOM, a CRLF line ending, or a missing shebang fails. This guarantees the published CLI is directly invokable on POSIX shells.","triggerScenarios":"content.startsWith('#!/usr/bin/env node\\n') is false at line 45. Triggered by a shebang like `#!/usr/bin/node`, `#!/bin/node`, `#!node`, a leading BOM/whitespace, or no shebang at all (e.g. the file is a plain module).","commonSituations":"A build pipeline that prepends a different shebang or none; Windows line endings (CRLF) breaking the `\\n` match; a copy step that strips the first line.","solutions":["Ensure the bundler/build emits the entry with the literal first line `#!/usr/bin/env node`.","Check for and strip a leading BOM or stray whitespace before the shebang.","Normalize line endings to LF in the build output."],"exampleFix":"// before (file head)\n#!/usr/bin/node\n...\n// after\n#!/usr/bin/env node\n...","handlingStrategy":"validation","validationCode":"const content = readFileSync(binPath, 'utf8')\nif (!content.startsWith('#!/usr/bin/env node\\n')) {\n  throw new Error('bin entry is missing the required #!/usr/bin/env node shebang')\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Configure the bundler to emit `#!/usr/bin/env node` as the literal first line.","Strip BOM and normalize to LF in build output."],"tags":["cli","packaging","shebang","unix","bin"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}