Detail Bug Report
https://app.detail.dev/org_befd6425-a158-4e24-9d4d-1e5c08769515/bugs/bug_d126f742-3d90-4d2d-8c7a-459f1fb4d15e
Introduced in e6844cf by @WilliamAGH on Sep 1, 2026
Summary
- Context:
commandUpdate (cli/bin/javachat.js:338-352) calls resolveNpmInstallTarget (cli/bin/javachat.js:355-416); the latter is introduced, and is the only code path that reaches the guard, by commit e6844cfb4 ("feat(cli): add self-update and clearer source gaps", 2026-09-01). git blame -L 355,416 cli/bin/javachat.js attributes every line of resolveNpmInstallTarget to e6844cfb4 (sole attribution); it is the last commit touching cli/bin/javachat.js. resolveNpmInstallTarget is called from exactly one site (commandUpdate, line 339) — no other subcommand reaches the guard.
- Bug:
javachat update exits 1 in an npm workspaces monorepo when @wcallahan/javachat-cli is declared in a workspace member package. npm hoists node_modules/.bin/javachat to the workspace root, but resolveNpmInstallTarget treats the .bin owner (root) as projectRoot, reads only the root package.json, and throws because the root does not declare the dependency.
- Actual vs. expected: Actual:
javachat update prints The project at <root> does not declare @wcallahan/javachat-cli; npm will not be allowed to modify it. and never invokes npm. Expected (per README wording “runs … npm install @wcallahan/javachat-cli@latest in that project”): when a workspace member declares the dependency, the update should run the equivalent npm install @wcallahan/javachat-cli@latest with cwd set to that member (or otherwise update the declaring project without polluting the root manifest).
- Impact: The
update convenience command cannot be used in the default npm-workspaces hoisted layout unless the user manually runs the install in the member directory (or uses npm install -w <member> ... / install-strategy=nested). Other CLI commands are unaffected.
Code with Bug
async function resolveNpmInstallTarget() {
const invokedEntrypoint = argv[1] ? resolve(argv[1]) : "";
if (basename(invokedEntrypoint) !== "javachat") {
throw new Error(/* ... */);
}
const invokedDirectory = dirname(invokedEntrypoint); // .../node_modules/.bin
if (basename(invokedDirectory) === ".bin" && basename(dirname(invokedDirectory)) === "node_modules") {
const projectRoot = dirname(dirname(invokedDirectory)); // <-- BUG 🔴 treats workspace root (hoisted .bin owner) as the project to update
if (projectRoot.split(sep).includes("_npx")) { throw new Error(/* ... */); }
const projectManifest = await readProjectManifest(projectRoot); // <-- BUG 🔴 only reads root package.json, ignores workspace members
const declaresJavaChat = [
projectManifest.dependencies,
projectManifest.devDependencies,
projectManifest.optionalDependencies,
].some((g) => Object.hasOwn(g ?? {}, CLI_PACKAGE));
if (!declaresJavaChat) {
throw new Error(
`The project at ${projectRoot} does not declare ${CLI_PACKAGE}; npm will not be allowed to modify it.`,
);
}
return {
npmArguments: ["install", `${CLI_PACKAGE}@latest`],
workingDirectory: projectRoot,
};
}
}
Explanation
In npm workspaces with default hoisting, node_modules/.bin/javachat is created at the workspace root even when the dependency is declared only in a member. The current logic infers projectRoot from the .bin directory and then validates the dependency declaration only in <root>/package.json. Because the root typically does not declare @wcallahan/javachat-cli in this setup, the guard fails and exits before running npm.
This is reproducible on supported Node/npm (Node v24.18.0, npm 11.16.0): after installing the CLI as a member dependency and ensuring .bin/javachat is hoisted to the root, javachat update refuses with the “does not declare” error and exit code 1.
Recommended Fix
When projectRoot/package.json does not declare @wcallahan/javachat-cli, do not immediately refuse if the root is a workspaces root; instead, consult workspace members and, if a member declares the dependency, run the existing command (npm install @wcallahan/javachat-cli@latest) with workingDirectory set to that member directory. Preserve the existing refusal as the fallback when enumeration fails or no member declares the package.
History
This bug was introduced in commit e6844cf. The commit "feat(cli): add self-update and clearer source gaps" added the entire self-update feature — commandUpdate and resolveNpmInstallTarget — in a single pass; neither function existed in the prior commit (655f3d09), and git log -S "resolveNpmInstallTarget" returns this commit alone.
Detail Bug Report
https://app.detail.dev/org_befd6425-a158-4e24-9d4d-1e5c08769515/bugs/bug_d126f742-3d90-4d2d-8c7a-459f1fb4d15e
Introduced in e6844cf by @WilliamAGH on Sep 1, 2026
Summary
commandUpdate(cli/bin/javachat.js:338-352) callsresolveNpmInstallTarget(cli/bin/javachat.js:355-416); the latter is introduced, and is the only code path that reaches the guard, by commite6844cfb4("feat(cli): add self-update and clearer source gaps", 2026-09-01).git blame -L 355,416 cli/bin/javachat.jsattributes every line ofresolveNpmInstallTargettoe6844cfb4(sole attribution); it is the last commit touchingcli/bin/javachat.js.resolveNpmInstallTargetis called from exactly one site (commandUpdate, line 339) — no other subcommand reaches the guard.javachat updateexits 1 in an npm workspaces monorepo when@wcallahan/javachat-cliis declared in a workspace member package. npm hoistsnode_modules/.bin/javachatto the workspace root, butresolveNpmInstallTargettreats the.binowner (root) asprojectRoot, reads only the rootpackage.json, and throws because the root does not declare the dependency.javachat updateprintsThe project at <root> does not declare @wcallahan/javachat-cli; npm will not be allowed to modify it.and never invokes npm. Expected (per README wording “runs …npm install @wcallahan/javachat-cli@latestin that project”): when a workspace member declares the dependency, the update should run the equivalentnpm install @wcallahan/javachat-cli@latestwithcwdset to that member (or otherwise update the declaring project without polluting the root manifest).updateconvenience command cannot be used in the default npm-workspaces hoisted layout unless the user manually runs the install in the member directory (or usesnpm install -w <member> .../install-strategy=nested). Other CLI commands are unaffected.Code with Bug
Explanation
In npm workspaces with default hoisting,
node_modules/.bin/javachatis created at the workspace root even when the dependency is declared only in a member. The current logic infersprojectRootfrom the.bindirectory and then validates the dependency declaration only in<root>/package.json. Because the root typically does not declare@wcallahan/javachat-cliin this setup, the guard fails and exits before running npm.This is reproducible on supported Node/npm (Node v24.18.0, npm 11.16.0): after installing the CLI as a member dependency and ensuring
.bin/javachatis hoisted to the root,javachat updaterefuses with the “does not declare” error and exit code 1.Recommended Fix
When
projectRoot/package.jsondoes not declare@wcallahan/javachat-cli, do not immediately refuse if the root is a workspaces root; instead, consult workspace members and, if a member declares the dependency, run the existing command (npm install @wcallahan/javachat-cli@latest) withworkingDirectoryset to that member directory. Preserve the existing refusal as the fallback when enumeration fails or no member declares the package.History
This bug was introduced in commit e6844cf. The commit "feat(cli): add self-update and clearer source gaps" added the entire self-update feature —
commandUpdateandresolveNpmInstallTarget— in a single pass; neither function existed in the prior commit (655f3d09), andgit log -S "resolveNpmInstallTarget"returns this commit alone.