scripts/check-version.js and the stop-time check scripts invoke external tools by bare name through a shell, with the session workspace as the current directory. Bare-name resolution through cmd.exe is current-directory-first, so where that leg is active, an executable planted in the opened repository runs instead of the intended tool — and on hosts that disable the cwd leg, behavior silently differs by environment.
Call sites (current main)
plugins/agent365/scripts/check-version.js runs at every session start:
const raw = execSync(
'gh release view --repo microsoft/agent365-skills --json tagName -q .tagName',
{ timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] }
).toString().trim();
The stop-time check scripts use the same shape via execSync(cmd, { cwd }) with cwd = the workspace:
hooks/stop/validate-a365-setup.js:39 — runCmd('a365 --version')
hooks/stop/validate-make-a365-agent.js:32 — runCmd('a365 --version')
hooks/stop/validate-add-workiq-tools.js:377,379 — runCmd('a365 --version'), runCmd('a365 develop list-configured')
hooks/stop/validate-test-local.js:56 — run('agentsplayground --version')
(scripts/install.js also spawns gh bare, but it is user-invoked, so it is lower priority.)
Why this is the wrong shape
On Windows, Node's execSync spawns cmd.exe /d /s /c, and cmd.exe resolves a bare command name from the current directory first, then PATH. These hooks run with cwd = the repository the user opened. Two consequences:
- Workspace-planted executables run wherever the cwd leg is active. A
gh.cmd / gh.exe / gh.bat at the root of the opened repository executes in place of the installed gh — at session start, before any user interaction. Its stdout also becomes plugin-visible text: check-version.js prints whatever gh returns as the "update available" version, with advice pointing at ./scripts/refresh.ps1. Reproduced by placing a gh.cmd that echoes a chosen version string into an empty workspace and running node scripts/check-version.js from that directory: the planted binary executes and the plugin renders its output as the update warning.
- Behavior is host-dependent. Some hosts set
NoDefaultCurrentDirectoryInExePath in hook environments (Claude Code does), which disables cmd.exe's current-directory lookup — the call then resolves from PATH only. So the same shipped code resolves names differently depending on the host and entry point (interactive session, manual node scripts/check-version.js, CI, other harnesses).
The a365 and agentsplayground call sites have the same property on the stop side (a365.cmd, agentsplayground.cmd in the workspace).
Suggested fix
Spawn an explicitly resolved executable without a shell:
const { execFileSync } = require('child_process');
// resolve gh against PATH only (walk process.env.PATH + the standard
// extensions; fail loudly if absent), then:
execFileSync(ghPath,
['release', 'view', '--repo', 'microsoft/agent365-skills', '--json', 'tagName', '-q', '.tagName'],
{ timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] });
A PATH-only resolver makes behavior identical across hosts and removes the current-directory leg everywhere, guarded or not. The same change applies to the a365 / agentsplayground sites and to install.js's spawnSync('gh', ...).
Related
#81 covers the frontmatter event-key casing that currently keeps these hooks from registering at all; the defects above apply to every execution path of these scripts regardless — the session-start script, the stop checks once they register, and any manual/CI invocation.
scripts/check-version.jsand the stop-time check scripts invoke external tools by bare name through a shell, with the session workspace as the current directory. Bare-name resolution through cmd.exe is current-directory-first, so where that leg is active, an executable planted in the opened repository runs instead of the intended tool — and on hosts that disable the cwd leg, behavior silently differs by environment.Call sites (current main)
plugins/agent365/scripts/check-version.jsruns at every session start:The stop-time check scripts use the same shape via
execSync(cmd, { cwd })withcwd= the workspace:hooks/stop/validate-a365-setup.js:39—runCmd('a365 --version')hooks/stop/validate-make-a365-agent.js:32—runCmd('a365 --version')hooks/stop/validate-add-workiq-tools.js:377,379—runCmd('a365 --version'),runCmd('a365 develop list-configured')hooks/stop/validate-test-local.js:56—run('agentsplayground --version')(
scripts/install.jsalso spawnsghbare, but it is user-invoked, so it is lower priority.)Why this is the wrong shape
On Windows, Node's
execSyncspawnscmd.exe /d /s /c, and cmd.exe resolves a bare command name from the current directory first, then PATH. These hooks run with cwd = the repository the user opened. Two consequences:gh.cmd/gh.exe/gh.batat the root of the opened repository executes in place of the installed gh — at session start, before any user interaction. Its stdout also becomes plugin-visible text:check-version.jsprints whateverghreturns as the "update available" version, with advice pointing at./scripts/refresh.ps1. Reproduced by placing agh.cmdthat echoes a chosen version string into an empty workspace and runningnode scripts/check-version.jsfrom that directory: the planted binary executes and the plugin renders its output as the update warning.NoDefaultCurrentDirectoryInExePathin hook environments (Claude Code does), which disables cmd.exe's current-directory lookup — the call then resolves from PATH only. So the same shipped code resolves names differently depending on the host and entry point (interactive session, manualnode scripts/check-version.js, CI, other harnesses).The
a365andagentsplaygroundcall sites have the same property on the stop side (a365.cmd,agentsplayground.cmdin the workspace).Suggested fix
Spawn an explicitly resolved executable without a shell:
A PATH-only resolver makes behavior identical across hosts and removes the current-directory leg everywhere, guarded or not. The same change applies to the
a365/agentsplaygroundsites and toinstall.js'sspawnSync('gh', ...).Related
#81 covers the frontmatter event-key casing that currently keeps these hooks from registering at all; the defects above apply to every execution path of these scripts regardless — the session-start script, the stop checks once they register, and any manual/CI invocation.