From 82b0672c05ece9fb0c8f48846cd8be331aaee145 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Mon, 18 May 2026 14:04:43 +0900 Subject: [PATCH] fix(git-master): emit csh-compatible setenv syntax for csh/tcsh shells buildShellAwareGitPrefix incorrectly returned raw bash VAR=value prefix for csh, which does not support inline env assignment. Now routes csh through buildEnvPrefix() to emit setenv syntax. Also sets code block lang to csh and skips bash block regex prefixing for csh. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- .../git-master-template-injection.test.ts | 69 +++++++++++++++++++ .../git-master-template-injection.ts | 10 +-- 2 files changed, 75 insertions(+), 4 deletions(-) diff --git a/src/features/opencode-skill-loader/git-master-template-injection.test.ts b/src/features/opencode-skill-loader/git-master-template-injection.test.ts index 06be6051e..a521e13f4 100644 --- a/src/features/opencode-skill-loader/git-master-template-injection.test.ts +++ b/src/features/opencode-skill-loader/git-master-template-injection.test.ts @@ -231,6 +231,18 @@ describe("#given buildShellAwareGitPrefix", () => { }) }) + describe("#when shell type is csh", () => { + it("#then returns csh setenv syntax", () => { + const result = buildShellAwareGitPrefix("GIT_MASTER=1", "csh") + expect(result).toBe("setenv GIT_MASTER 1;") + }) + + it("#then handles multiple env vars", () => { + const result = buildShellAwareGitPrefix("CI=true GIT_MASTER=1", "csh") + expect(result).toBe("setenv CI true; setenv GIT_MASTER 1;") + }) + }) + describe("#when prefix is empty", () => { it("#then returns empty string", () => { const result = buildShellAwareGitPrefix("", "powershell") @@ -319,4 +331,61 @@ describe("#given PowerShell shell detection in injectGitMasterConfig", () => { expect(result).not.toContain("$env:") }) }) + + describe("#when shell is csh (SHELL set to /bin/csh)", () => { + it("#then emits setenv prefix syntax in csh code block", () => { + process.env.SHELL = "/bin/csh" + delete process.env.PSModulePath + delete process.env.MSYSTEM + Object.defineProperty(process, "platform", { value: "linux" }) + + const result = injectGitMasterConfig(SAMPLE_TEMPLATE, { + commit_footer: false, + include_co_authored_by: false, + git_env_prefix: "GIT_MASTER=1", + }) + + expect(result).toContain("setenv GIT_MASTER 1;") + expect(result).toContain("```csh") + expect(result).not.toContain("```bash\nsetenv") + }) + + it("#then does NOT prefix bash code blocks with setenv syntax", () => { + process.env.SHELL = "/bin/csh" + delete process.env.PSModulePath + delete process.env.MSYSTEM + Object.defineProperty(process, "platform", { value: "linux" }) + + const result = injectGitMasterConfig(SAMPLE_TEMPLATE, { + commit_footer: false, + include_co_authored_by: false, + git_env_prefix: "GIT_MASTER=1", + }) + + const bashBlockMatch = result.match(/```bash\r?\n([\s\S]*?)```/g) + if (bashBlockMatch) { + for (const block of bashBlockMatch) { + expect(block).not.toContain("setenv") + } + } + }) + }) + + describe("#when shell is tcsh (SHELL set to /usr/local/bin/tcsh)", () => { + it("#then emits setenv prefix syntax in csh code block", () => { + process.env.SHELL = "/usr/local/bin/tcsh" + delete process.env.PSModulePath + delete process.env.MSYSTEM + Object.defineProperty(process, "platform", { value: "darwin" }) + + const result = injectGitMasterConfig(SAMPLE_TEMPLATE, { + commit_footer: false, + include_co_authored_by: false, + git_env_prefix: "GIT_MASTER=1", + }) + + expect(result).toContain("setenv GIT_MASTER 1;") + expect(result).toContain("```csh") + }) + }) }) diff --git a/src/features/opencode-skill-loader/git-master-template-injection.ts b/src/features/opencode-skill-loader/git-master-template-injection.ts index 0483c2fc0..e1b1b05ba 100644 --- a/src/features/opencode-skill-loader/git-master-template-injection.ts +++ b/src/features/opencode-skill-loader/git-master-template-injection.ts @@ -26,15 +26,17 @@ export function parseBashEnvPrefix(prefix: string): Record { /** * Build the shell-aware command prefix for git commands. * Uses the shared shell detection and env prefix builder to emit correct syntax - * for PowerShell ($env:VAR='value';), cmd (set VAR="value" &&), or unix (VAR=value). + * for PowerShell ($env:VAR='value';), cmd (set VAR="value" &&), + * csh (setenv VAR value;), or unix (VAR=value). * * For unix shells, we use the inline VAR=value prefix style (not export) to match * the original behavior where the env var applies only to the immediately following command. + * For csh/tcsh, we use setenv syntax since csh does not support inline VAR=value. */ export function buildShellAwareGitPrefix(bashPrefix: string, shellType?: ShellType): string { if (!bashPrefix) return "" const resolvedShellType = shellType ?? detectShellType() - if (resolvedShellType === "unix" || resolvedShellType === "csh") { + if (resolvedShellType === "unix") { return bashPrefix } const envRecord = parseBashEnvPrefix(bashPrefix) @@ -48,8 +50,8 @@ export function injectGitMasterConfig(template: string, config?: GitMasterConfig const shellType = detectShellType() const shellPrefix = gitEnvPrefix ? buildShellAwareGitPrefix(gitEnvPrefix, shellType) : "" - const codeBlockLang = shellType === "powershell" ? "pwsh" : "bash" - const skipBashBlockPrefixing = shellType === "powershell" || shellType === "cmd" + const codeBlockLang = shellType === "powershell" ? "pwsh" : shellType === "csh" ? "csh" : "bash" + const skipBashBlockPrefixing = shellType === "powershell" || shellType === "cmd" || shellType === "csh" let result = gitEnvPrefix ? injectGitEnvPrefix(template, shellPrefix, codeBlockLang) : template