The stop-time build checks run npx tsc --noEmit with cwd = the project being checked. On a project without a local TypeScript install, npm fetches the compiler on demand — and it resolves the package and the registry from that same project's files, including a workspace-root .npmrc that redirects the registry. The project under check effectively chooses where the plugin's build-check compiler is downloaded from, and the fetched binary's exit code decides whether the check passes.
Call sites (current main)
plugins/agent365/hooks/stop/validate-make-ai-teammate.js:317 — runBuild('npx tsc --noEmit', 15000)
plugins/agent365/hooks/stop/validate-add-workiq-tools.js:412
plugins/agent365/hooks/stop/validate-instrument-observability.js:339
runBuild is execSync(cmd, { cwd, timeout, stdio: 'pipe' }) with cwd set to the workspace.
What happens on a project without a local typescript
npx tsc finds no local tsc binary, so npm exec auto-installs the npm package named tsc — non-interactively in this stdio shape; the only announcement is a warning line:
npm warn exec The following package was not found and will be installed: tsc@2.0.4
Two distinct problems:
- The check stops checking with TypeScript. The npm package
tsc is not TypeScript — TypeScript ships as the typescript package, which provides the tsc bin. On a clean project, the "TypeScript compilation failed" gate is actually decided by an unrelated third-party package's exit code.
- The registry comes from the project being checked. npm resolves per-project configuration from the current directory: a
.npmrc at the workspace root (registry=...) selects where the package is fetched from. The whole point of a stop-time check is to run carefully against project content; letting the project's own config file pick the download source for the checking tool inverts that.
Observed shape
On a clean machine: an empty project containing only a .npmrc pointing registry at a local test registry (loopback), a package.json, and a .ts file; running node validate-make-ai-teammate.js in that directory — npm fetched the package named tsc from the loopback registry and executed its bin with --noEmit, cwd = the project. The fetched binary's exit code fed the check result (no "TypeScript compilation failed" issue was raised when it exited 0). With the ambient registry instead of the redirected one, the same run fetched tsc@2.0.4 from the public registry with the warning line above — same mechanism, different source.
Suggested fix
Resolve the compiler from the plugin's own pinned toolchain instead of ambient npx resolution, for example:
- declare
typescript as a dependency of the plugin and invoke node <plugin root>/node_modules/typescript/bin/tsc --noEmit directly (argv, no shell), or
- at minimum,
npx --yes typescript@<pinned version> with npm_config_registry set explicitly by the plugin so workspace config cannot redirect it.
The dotnet build legs of the same scripts inherit cwd too; dotnet is a fixed-name SDK binary rather than a package fetch, but the explicit-resolution pattern applies there as well.
Related
#81 covers the frontmatter event-key casing that currently keeps these stop hooks from registering; the toolchain issue above applies to any execution path of these check scripts, and to the checks themselves once they register.
The stop-time build checks run
npx tsc --noEmitwithcwd= the project being checked. On a project without a local TypeScript install, npm fetches the compiler on demand — and it resolves the package and the registry from that same project's files, including a workspace-root.npmrcthat redirects the registry. The project under check effectively chooses where the plugin's build-check compiler is downloaded from, and the fetched binary's exit code decides whether the check passes.Call sites (current main)
plugins/agent365/hooks/stop/validate-make-ai-teammate.js:317—runBuild('npx tsc --noEmit', 15000)plugins/agent365/hooks/stop/validate-add-workiq-tools.js:412plugins/agent365/hooks/stop/validate-instrument-observability.js:339runBuildisexecSync(cmd, { cwd, timeout, stdio: 'pipe' })withcwdset to the workspace.What happens on a project without a local typescript
npx tscfinds no localtscbinary, so npm exec auto-installs the npm package namedtsc— non-interactively in this stdio shape; the only announcement is a warning line:Two distinct problems:
tscis not TypeScript — TypeScript ships as thetypescriptpackage, which provides thetscbin. On a clean project, the "TypeScript compilation failed" gate is actually decided by an unrelated third-party package's exit code..npmrcat the workspace root (registry=...) selects where the package is fetched from. The whole point of a stop-time check is to run carefully against project content; letting the project's own config file pick the download source for the checking tool inverts that.Observed shape
On a clean machine: an empty project containing only a
.npmrcpointingregistryat a local test registry (loopback), apackage.json, and a.tsfile; runningnode validate-make-ai-teammate.jsin that directory — npm fetched the package namedtscfrom the loopback registry and executed its bin with--noEmit,cwd= the project. The fetched binary's exit code fed the check result (no "TypeScript compilation failed" issue was raised when it exited 0). With the ambient registry instead of the redirected one, the same run fetchedtsc@2.0.4from the public registry with the warning line above — same mechanism, different source.Suggested fix
Resolve the compiler from the plugin's own pinned toolchain instead of ambient npx resolution, for example:
typescriptas a dependency of the plugin and invokenode <plugin root>/node_modules/typescript/bin/tsc --noEmitdirectly (argv, no shell), ornpx --yes typescript@<pinned version>withnpm_config_registryset explicitly by the plugin so workspace config cannot redirect it.The
dotnet buildlegs of the same scripts inheritcwdtoo;dotnetis a fixed-name SDK binary rather than a package fetch, but the explicit-resolution pattern applies there as well.Related
#81 covers the frontmatter event-key casing that currently keeps these stop hooks from registering; the toolchain issue above applies to any execution path of these check scripts, and to the checks themselves once they register.