fix(install): chmod sdk dist/cli.js executable; fix context monitor over-reporting (#2460)

Bug #2453: After tsc builds sdk/dist/cli.js, npm install -g from a local
directory does not chmod the bin-script target (unlike tarball extraction).
The file lands at mode 644, the gsd-sdk symlink points at a non-executable
file, and command -v gsd-sdk fails on every first install. Fix: explicitly
chmodSync(cliPath, 0o755) immediately after npm install -g completes,
mirroring the pattern used for hook files throughout the installer.

Bug #2451: gsd-context-monitor warning messages over-reported usage by ~13
percentage points vs CC native /context. Root cause: gsd-statusline.js
wrote a buffer-normalized used_pct (accounting for the 16.5% autocompact
reserve) to the bridge file, inflating values. The bridge used_pct is now
raw (Math.round(100 - remaining_percentage)), consistent with what CC's
native /context command reports. The statusline progress bar continues to
display the normalized value; only the bridge value changes. Updated the
existing #2219 tests to check the normalized display via hook stdout rather
than bridge.used_pct, and added a new assertion that bridge.used_pct is raw.

Closes #2453
Closes #2451

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-04-20 10:08:46 -04:00
committed by GitHub
parent 53b9fba324
commit 0ea443cbcf
5 changed files with 346 additions and 22 deletions

View File

@@ -6786,6 +6786,26 @@ function installSdkIfNeeded() {
emitSdkFatal('Failed to `npm install -g .` from sdk/.', { globalBin: null, exitCode: 1 });
}
// 3a. Explicitly chmod dist/cli.js to 0o755 in the global install location.
// `tsc` emits files at process umask (typically 0o644 — non-executable), and
// `npm install -g` from a local directory does NOT chmod bin-script targets the
// way tarball extraction does. Without this, the `gsd-sdk` bin symlink points at
// a non-executable file and `command -v gsd-sdk` fails on every first install
// (root cause of #2453). Mirrors the pattern used for hook files in this installer.
try {
const prefixRes = spawnSync(npmCmd, ['config', 'get', 'prefix'], { encoding: 'utf-8' });
if (prefixRes.status === 0) {
const npmPrefix = (prefixRes.stdout || '').trim();
const sdkPkg = JSON.parse(fs.readFileSync(path.join(sdkDir, 'package.json'), 'utf-8'));
const sdkName = sdkPkg.name; // '@gsd-build/sdk'
const globalModulesDir = process.platform === 'win32'
? path.join(npmPrefix, 'node_modules')
: path.join(npmPrefix, 'lib', 'node_modules');
const cliPath = path.join(globalModulesDir, sdkName, 'dist', 'cli.js');
try { fs.chmodSync(cliPath, 0o755); } catch (e) { /* Windows / path not found */ }
}
} catch (e) { /* Non-fatal: PATH verification in step 4 will catch any real failure */ }
// 4. Verify gsd-sdk is actually resolvable on PATH. npm's global bin dir is
// not always on the current shell's PATH (Homebrew prefixes, nvm setups,
// unconfigured npm prefix), so a zero exit status from `npm install -g`