{"record":{"id":"6ca449d53051ad7d","repo":"tinyhumansai/openhuman","slug":"refusing-to-overwrite-hidden-file-outputpath","errorCode":null,"errorMessage":"Refusing to overwrite hidden file: ${outputPath}","messagePattern":"Refusing to overwrite hidden file: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"scripts/release/generate-release-notes.mjs","lineNumber":703,"sourceCode":"  });\n  const title = releaseTitle(from, options.to, resolvedTo);\n\n  let markdown;\n  if (options.dryRun) {\n    const request = buildOpenAiRequest({ model: options.model, title, payload });\n    markdown = JSON.stringify(request, null, 2);\n  } else if (options.noAi) {\n    markdown = renderDeterministicNotes({ title, payload });\n  } else {\n    const request = buildOpenAiRequest({ model: options.model, title, payload });\n    markdown = await summarizeWithOpenAi(request);\n    markdown = ensureAllPullRequestsLinked(markdown, payload.pullRequests);\n  }\n\n  if (options.output) {\n    const outputPath = resolve(options.output);\n    if (existsSync(outputPath) && basename(outputPath).startsWith('.')) {\n      throw new Error(`Refusing to overwrite hidden file: ${outputPath}`);\n    }\n    writeFileSync(outputPath, markdown.endsWith('\\n') ? markdown : `${markdown}\\n`);\n    console.error(`[release-notes] Wrote ${outputPath}`);\n  } else {\n    process.stdout.write(markdown.endsWith('\\n') ? markdown : `${markdown}\\n`);\n  }\n}\n\nif (import.meta.url === pathToFileURL(process.argv[1]).href) {\n  main().catch((error) => {\n    console.error(`[release-notes] ${error.message}`);\n    process.exit(1);\n  });\n}\n","sourceCodeStart":685,"sourceCodeEnd":718,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/a221052e0df5b1f7598fceba7329fd1af95d6699/scripts/release/generate-release-notes.mjs#L685-L718","documentation":"A write-guard in main(): when --output names a path that already exists AND basename(outputPath) starts with '.' (a dotfile such as .env, .gitignore, .release-notes.md), the script refuses to overwrite it, protecting hidden config files from being clobbered by generated Markdown.","triggerScenarios":"Passing --output .env, --output .draft.md, or any path where a hidden file already exists — typically a scripted or copy-pasted output path that collides with a dotfile. Writing a new hidden file is fine; only overwriting an existing one is refused.","commonSituations":"Shell variables or globs resolving to a dotfile; a stale hidden draft from an earlier experiment still sitting at the target; muscle-memory use of dot-prefixed scratch filenames.","solutions":["Point --output at a regular, non-hidden filename (e.g. release-notes.md)","If overwriting the hidden file is truly intended, remove or rename it yourself first — the script will not do it"],"exampleFix":"# before (hidden draft already exists → refused)\nnode scripts/release/generate-release-notes.mjs --output .release-notes.md\n\n# after\nnode scripts/release/generate-release-notes.mjs --output release-notes.md","handlingStrategy":"validation","validationCode":"import { basename, resolve } from 'node:path';\nimport { existsSync } from 'node:fs';\nconst out = resolve(options.output);\nif (existsSync(out) && basename(out).startsWith('.')) {\n  throw new Error(`refusing hidden output ${out}; choose a visible filename`);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["keep release notes in non-hidden paths","treat the refusal as intentional — never script around it blindly","validate scripted --output values against dotfiles before invocation"],"tags":["filesystem","safety-guard","release-notes","dotfiles"],"backgroundTag":null,"analyzedSha":"a221052e0df5b1f7598fceba7329fd1af95d6699","analyzedAt":"2026-08-16T12:47:06.542Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}