fix(install): guard writeSettings against null settingsPath for cline runtime (#2035)

* fix(install): guard writeSettings against null settingsPath for cline runtime

Cline returns settingsPath: null from install() because it uses .clinerules
instead of settings.json. The finishInstall() guard was missing !isCline,
causing a crash with ERR_INVALID_ARG_TYPE when installing with the cline runtime.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* test(cline): add regression tests for ERR_INVALID_ARG_TYPE null settingsPath guard

Adds two regression tests to tests/cline-install.test.cjs for gsd-build/get-shit-done#2044:
- Assert install(false, 'cline') does not throw ERR_INVALID_ARG_TYPE
- Assert settings.json is not written for cline runtime

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

* test(cline): fix regression tests to directly call finishInstall with null settingsPath

The previous regression tests called install() which returns early for cline
before reaching finishInstall(), so the crash was never exercised. Fix by:
- Exporting finishInstall from bin/install.js
- Calling finishInstall(null, null, ..., 'cline') directly so the null
  settingsPath guard is actually tested

Tests now fail (ERR_INVALID_ARG_TYPE) without the fix and pass with it.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Berkay Karaman
2026-04-10 20:58:16 +03:00
committed by GitHub
parent 50537e5f67
commit d85a42c7ad
2 changed files with 21 additions and 1 deletions

View File

@@ -6130,6 +6130,7 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
const isCursor = runtime === 'cursor';
const isWindsurf = runtime === 'windsurf';
const isTrae = runtime === 'trae';
const isCline = runtime === 'cline';
if (shouldInstallStatusline && !isOpencode && !isKilo && !isCodex && !isCopilot && !isCursor && !isWindsurf && !isTrae) {
settings.statusLine = {
@@ -6140,7 +6141,7 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
}
// Write settings when runtime supports settings.json
if (!isCodex && !isCopilot && !isKilo && !isCursor && !isWindsurf && !isTrae) {
if (!isCodex && !isCopilot && !isKilo && !isCursor && !isWindsurf && !isTrae && !isCline) {
writeSettings(settingsPath, settings);
}
@@ -6489,6 +6490,7 @@ if (process.env.GSD_TEST_MODE) {
validateHookFields,
preserveUserArtifacts,
restoreUserArtifacts,
finishInstall,
};
} else {

View File

@@ -29,6 +29,7 @@ const {
getConfigDirFromHome,
convertClaudeToCliineMarkdown,
install,
finishInstall,
} = require('../bin/install.js');
describe('Cline runtime directory mapping', () => {
@@ -154,6 +155,23 @@ describe('Cline install (local)', () => {
assert.ok(fs.existsSync(engineDir), 'get-shit-done directory must exist after install');
});
test('finishInstall does not throw ERR_INVALID_ARG_TYPE for cline runtime (regression: null settingsPath guard)', () => {
// install() returns settingsPath: null for cline — finishInstall() must not call
// writeSettings(null, ...) or it crashes with ERR_INVALID_ARG_TYPE.
// Before fix: isCline was missing from the writeSettings guard in finishInstall().
// After fix: !isCline is in the guard, matching codex/copilot/cursor/windsurf/trae.
assert.doesNotThrow(
() => finishInstall(null, null, null, false, 'cline', false, tmpDir),
'finishInstall must not throw when called with null settingsPath for cline runtime'
);
});
test('settings.json is not written for cline runtime', () => {
finishInstall(null, null, null, false, 'cline', false, tmpDir);
const settingsJson = path.join(tmpDir, 'settings.json');
assert.ok(!fs.existsSync(settingsJson), 'settings.json must not be written for cline runtime');
});
test('installed engine files have no leaked .claude paths', () => {
install(false, 'cline');
const engineDir = path.join(tmpDir, 'get-shit-done');