fix(update): stop false "development build" block on npm installs with NODE_ENV=development (#1781)

`/update` and `openclaude update` reported "Auto-update is unavailable for
a development build." even when OpenClaude was correctly installed from npm,
as long as the launching shell had `NODE_ENV=development` exported.

Root cause: `getCurrentInstallationType()` checked `NODE_ENV === 'development'`
as its first branch, before any path-based detection. A user's shell env var
then downgraded a real npm install to 'development', which routed
`resolveUpdateStrategy()` to `{ action: 'blocked', reason: 'development' }`.

Two-part fix:

1. doctorDiagnostic.ts — move the `NODE_ENV === 'development'` check to a
   fallback position after all real-install path markers (bundled mode, local
   npm, npm-global paths, /npm/, /nvm/, `npm config get prefix`). Path
   detection runs first; NODE_ENV only classifies as 'development' when no
   install path matches (i.e. an actual source-tree `bun run dev` run).

2. bin/openclaude — the heap-sizing relaunch previously used
   `fileURLToPath(import.meta.url)`, which resolves symlinks. After relaunch,
   `process.argv[1]` pointed at the real file target (repo path for
   `npm install -g .`, package path inside node_modules for real installs),
   defeating path-based detection. Preserve `process.argv[1]` (the original
   invocation path, e.g. /usr/local/bin/openclaude or nvm bin symlink) so
   npm-global path markers can match correctly.

Verified: `bun run typecheck` passes; `openclaude doctor` now reports
npm-global (not development) with NODE_ENV=development set on a real
npm global install.

Co-authored-by: OpenClaude <openclaude@gitlawb.com>
This commit is contained in:
Kevin Codex
2026-06-25 06:52:58 +08:00
committed by GitHub
co-authored by OpenClaude
parent 28bbec4948
commit d32b6f0476
2 changed files with 20 additions and 5 deletions
+11 -1
View File
@@ -75,9 +75,19 @@ function relaunchWithLongSessionHeapIfNeeded() {
arg => !arg.startsWith('--max-memory=') && arg !== '--max-memory',
)
// Preserve the original argv[1] (which may be a symlink like
// /usr/local/bin/openclaude) instead of resolving it via import.meta.url.
// Resolving symlinks here defeats install-type detection downstream: a real
// npm global install (symlink → node_modules/@gitlawb/openclaude/bin) would
// resolve to the package's real path inside node_modules, which is fine, but
// a `npm install -g .` dev symlink resolves back to the repo and looks like
// a source-tree dev run. Using argv[1] keeps the invocation path stable so
// doctorDiagnostic's npm-global path markers can match correctly.
const launcherPath = process.argv[1] || fileURLToPath(import.meta.url)
const result = spawnSync(process.execPath, [
...execArgv,
fileURLToPath(import.meta.url),
launcherPath,
...childArgs,
], {
stdio: 'inherit',
+9 -4
View File
@@ -99,10 +99,6 @@ function getNormalizedPaths(): [invokedPath: string, execPath: string] {
}
export async function getCurrentInstallationType(): Promise<InstallationType> {
if (process.env.NODE_ENV === 'development') {
return 'development'
}
const [invokedPath] = getNormalizedPaths()
// Check if running in bundled mode first
@@ -158,6 +154,15 @@ export async function getCurrentInstallationType(): Promise<InstallationType> {
return 'npm-global'
}
// Development build: running from a source tree (e.g. `bun run dev`) with
// NODE_ENV=development. Checked AFTER all real-install path markers so that
// a user shell exporting NODE_ENV=development can't downgrade a real npm
// install to 'development' (which would block /update). A source-tree run
// matches none of the path markers above, so it lands here.
if (process.env.NODE_ENV === 'development') {
return 'development'
}
// If we can't determine, return unknown
return 'unknown'
}