From d85a42c7ad6aa4f5b6b416c4d5295e94eddd36c8 Mon Sep 17 00:00:00 2001 From: Berkay Karaman Date: Fri, 10 Apr 2026 20:58:16 +0300 Subject: [PATCH] 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) * 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) * 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) --------- Co-authored-by: Claude Sonnet 4.6 (1M context) --- bin/install.js | 4 +++- tests/cline-install.test.cjs | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/bin/install.js b/bin/install.js index 063752c1..c08cdae3 100755 --- a/bin/install.js +++ b/bin/install.js @@ -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 { diff --git a/tests/cline-install.test.cjs b/tests/cline-install.test.cjs index e0ed7e4c..383b89c6 100644 --- a/tests/cline-install.test.cjs +++ b/tests/cline-install.test.cjs @@ -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');