Skip to content

fix: windows command execution#40

Merged
loco1842 merged 15 commits into
mainfrom
fix/windows-command-execution
May 8, 2026
Merged

fix: windows command execution#40
loco1842 merged 15 commits into
mainfrom
fix/windows-command-execution

Conversation

@loco1842
Copy link
Copy Markdown
Owner

@loco1842 loco1842 commented May 4, 2026

Summary by CodeRabbit

  • Bug Fixes

    • Script execution now respects the configured shell (not assumed bash) and runs consistently across platforms.
    • Shebang handling generalized: legacy shebangs are stripped for compatibility; scripts without content are treated as empty.
    • Generated scripts now trim extra whitespace and always end with a single trailing newline.
  • Tests

    • Added comprehensive tests for script generation, parsing, and template variable handling.

loco1842 added 10 commits May 4, 2026 14:42
- Remove const scriptHeader '#!/bin/bash' from script.go
- GenerateScript() returns body without shebang prefix, empty input returns empty string
- ParseScriptBody() generically strips any #! prefix line for backward compat
- Both old (#!/bin/bash in DB) and new (no shebang) scripts handled transparently
…sage

- Add platform-appropriate shebang in ExecuteScript: #!/bin/sh on Unix, none on Windows
- Strip existing shebangs from stored content before adding platform shebang (backward compat)
- Fix Unix execution path to use -lc flag (was previously dropped, cmd ran without login shell)
- Add stripShebang helper for generic #! line removal
- Both platforms now uniformly use e.flag in exec.CommandContext
- Remove hardcoded #!/bin/bash from stored scripts
- Platform-aware shebang injection at execution time (#!/bin/sh on Unix, none on Windows)
- Fix Unix -lc flag usage for login shell
- Generic #! stripping for backward compat with old DB records
- No changes needed to execution_service.go (callers transparently compatible)
…form-appropriate shell name

- Convert BuildFinalCommand from package-level function to method on *Executor
- Use e.shell basename instead of hardcoded "bash"
- Windows displays "cmd <script>", Unix displays "sh"/"zsh"/"bash"/" <script>" per $SHELL
- Fallback to "sh" if shell name is empty
…y strings plan

- Convert BuildFinalCommand to *Executor method with e.shell basename
- No more hardcoded 'bash' in display output
- BuildFinalCommand confirmed dead code (zero callers); BuildDisplayCommand handles execution history display
- STATE.md updated: plan 2/3 complete, decisions recorded
…ctions covering GenerateScript, ParseScriptBody, ExtractTemplateVars, ReplaceTemplateVars, MergeDetectedVars

- TestGenerateScript: verifies no shebang prefix, handles empty/whitespace/multi-line
- TestParseScriptBody: verifies backward compat with #!/bin/bash and #!/usr/bin/env bash, no-shebang format, empty, shebang-only
- TestExtractTemplateVars: verifies variable extraction and deduplication
- TestReplaceTemplateVars: verifies template substitution with known and unknown vars
- TestMergeDetectedVars: verifies merge order (detected first, then manual-only)
- Fix ParseScriptBody edge case: shebang-only input now returns empty string
…project checks

- Created script_test.go with 5 test functions covering script generation, parsing, template vars
- Fixed ParseScriptBody edge case for shebang-only inputs
- All 8 Go tests pass, make check passes (go build + pnpm tsc)
- Wails bindings verified up to date
-c flag treats the file path as a command string, requiring +x permission.
-l alone tells the shell to read the file as a login shell script.
Only affects ExecuteScript; terminal launchers use -lc correctly with command strings.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 4, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 79551dd4-30bf-443d-9937-a2bbe2689622

📥 Commits

Reviewing files that changed from the base of the PR and between b3996ba and 8a227fa.

📒 Files selected for processing (2)
  • db_test.go
  • executor.go
✅ Files skipped from review due to trivial changes (1)
  • db_test.go

📝 Walkthrough

Walkthrough

Executor and script handling were changed to generalize shebang stripping and unify script execution. BuildFinalCommand became an Executor method deriving the shell display name; scripts no longer get a hardcoded #!/bin/bash; helpers and tests updated for the new behavior.

Changes

Script / Executor Shebang and Execution

Layer / File(s) Summary
API / Signature
executor.go
Package-level BuildFinalCommand removed; added func (e *Executor) BuildFinalCommand(variables map[string]string) string that derives display shell name from e.shell (basename, fallback sh).
Script Body Handling
script.go
GenerateScript stops prepending #!/bin/bash and returns trimmed body with a single trailing newline (or empty string). ParseScriptBody now strips any leading #!... first line rather than only #!/bin/bash variants.
Shebang Helper
executor.go
Added unexported stripShebang(content string) string which trims and removes a leading shebang line (returns remaining body or "" if only a shebang existed).
Execution Wiring
executor.go
ExecuteScript uses stripShebang, prepends #!/bin/sh on non-Windows before writing the temp file, and always invokes e.shell with e.flag plus the temp script path instead of executing the temp file directly. OpenInTerminal updated to use stripShebang.
Tests
db_test.go
Added unit tests: TestGenerateScript, TestParseScriptBody, TestExtractTemplateVars, TestReplaceTemplateVars, TestMergeDetectedVars covering script generation/parsing and template variable helpers.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Executor
    participant FS as FileSystem
    participant Shell
    Client->>Executor: request to run scriptContent + vars
    Executor->>Executor: stripShebang(scriptContent)
    alt non-Windows
        Executor->>FS: write temp file with "#!/bin/sh\n" + body
    else Windows
        Executor->>FS: write temp file with body
    end
    Executor->>Shell: start process `e.shell` with `e.flag` and tempPath
    Shell->>FS: read/execute tempPath
    Shell-->>Executor: exit code / output
    Executor-->>Client: result
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • fix/preset preview #3: Modifies shebang handling and command display generation, overlapping with this PR's refactored script and executor changes.

Poem

🐰 I trimmed the shebang, hopped through the shell,
Temp files lined up and executed well,
Variables danced, tests gave a cheer,
A tidy run — now the path is clear! 🥕

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title 'fix: windows command execution' is misleading; the PR addresses cross-platform script execution (Unix shell handling, shebang stripping) rather than Windows-specific fixes. Revise the title to reflect the actual scope, such as 'fix: unify script execution with shell-agnostic shebang handling' or 'fix: cross-platform script execution with shell-agnostic shebang support'.
Docstring Coverage ⚠️ Warning Docstring coverage is 26.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 60 minutes.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
script_test.go (1)

1-129: ⚡ Quick win

Keep new Go tests in db_test.go, or update the guideline in this PR too.

These cases are valuable, but adding script_test.go diverges from the current repo test-file contract. If the project is intentionally moving away from that layout, the guideline should change in the same PR.

As per coding guidelines, "*_test.go: Go tests are in db_test.go only; run with go test ./...; three core migration tests exist".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@script_test.go` around lines 1 - 129, The new tests in script_test.go
(TestGenerateScript, TestParseScriptBody, TestExtractTemplateVars,
TestReplaceTemplateVars, TestMergeDetectedVars) violate the repo guideline that
all Go tests belong in db_test.go; either move these test functions into
db_test.go (preserving their names and test logic and updating imports if
needed) or update the repository guideline in this PR to accept additional
*_test.go files; ensure you modify the same PR to reflect the chosen approach so
the repo contract remains consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@executor.go`:
- Around line 113-116: The current Unix path injects a "#!/bin/sh" and then
spawns the shell with the temp-file as an argument, which breaks the executor
contract; instead, remove the shebang injection (do not prepend "#!/bin/sh" to
scriptContent) and on non-Windows use the user's shell: pick shell :=
os.Getenv("SHELL"); if empty set shell = "/bin/sh", then exec shell with args
"-lc" and stream scriptContent to the shell's stdin (so the shell interprets the
script body), while on Windows use the cmd "/C" execution path as before; adjust
the code that builds and runs the command (references: scriptContent and the
runtime.GOOS check) to implement this behavior.
- Around line 219-229: stripShebang currently returns the raw shebang line when
the content contains only a shebang without a trailing newline, causing a
shebang-only stored script to be executed; update stripShebang (the function
named stripShebang) to mirror ParseScriptBody behavior by returning an empty
string when a shebang is present but no newline follows (i.e., if
strings.HasPrefix(s, "#!") and strings.Index(s, "\n") == -1 then return ""),
otherwise continue returning the text after the first newline as before.

In `@script.go`:
- Around line 11-18: GenerateScript currently strips all whitespace and returns
only the body, breaking the storage contract that persisted scripts must include
the "#!/bin/bash" shebang; change GenerateScript so that for non-empty bodies it
trims whitespace but prepends the shebang line ("#!/bin/bash\n") before
returning, while leaving ParseScriptBody responsible for removing the shebang
for the editor/runtime split; update GenerateScript to return "" for empty
bodies and "#!/bin/bash\n" + body + "\n" (or equivalent) for non-empty scripts
to preserve the persisted format.

---

Nitpick comments:
In `@script_test.go`:
- Around line 1-129: The new tests in script_test.go (TestGenerateScript,
TestParseScriptBody, TestExtractTemplateVars, TestReplaceTemplateVars,
TestMergeDetectedVars) violate the repo guideline that all Go tests belong in
db_test.go; either move these test functions into db_test.go (preserving their
names and test logic and updating imports if needed) or update the repository
guideline in this PR to accept additional *_test.go files; ensure you modify the
same PR to reflect the chosen approach so the repo contract remains consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 8bba1f81-279a-4c47-8874-38d1553e893b

📥 Commits

Reviewing files that changed from the base of the PR and between 7a37ad9 and 3ecd9fa.

⛔ Files ignored due to path filters (2)
  • .planning/ROADMAP.md is excluded by !.planning/*
  • .planning/STATE.md is excluded by !.planning/*
📒 Files selected for processing (3)
  • executor.go
  • script.go
  • script_test.go

Comment thread executor.go
Comment on lines +113 to +116
// Add platform-appropriate shebang at execution time
if runtime.GOOS != "windows" {
scriptContent = "#!/bin/sh\n" + scriptContent
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Restore the Unix $SHELL -lc execution path.

Line 131 hardcodes -l and passes the temp file as a script argument. That no longer honors the executor contract, and the injected #!/bin/sh is ignored because the user's shell parses the file itself. On non-POSIX login shells, valid script bodies will now fail.

As per coding guidelines, "Platform-aware shell execution must use $SHELL -lc on Unix (falls back to /bin/sh), cmd /C on Windows".

Also applies to: 127-132

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@executor.go` around lines 113 - 116, The current Unix path injects a
"#!/bin/sh" and then spawns the shell with the temp-file as an argument, which
breaks the executor contract; instead, remove the shebang injection (do not
prepend "#!/bin/sh" to scriptContent) and on non-Windows use the user's shell:
pick shell := os.Getenv("SHELL"); if empty set shell = "/bin/sh", then exec
shell with args "-lc" and stream scriptContent to the shell's stdin (so the
shell interprets the script body), while on Windows use the cmd "/C" execution
path as before; adjust the code that builds and runs the command (references:
scriptContent and the runtime.GOOS check) to implement this behavior.

Comment thread script.go
Comment on lines +11 to +18
// GenerateScript returns the script body trimmed of surrounding whitespace with a trailing newline.
// No shebang is prepended — the executor adds a platform-appropriate shebang at execution time.
func GenerateScript(body string) string {
body = strings.TrimSpace(body)
return scriptHeader + "\n\n" + body + "\n"
if body == "" {
return ""
}
return body + "\n"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve the persisted #!/bin/bash header.

GenerateScript now stores a bare body, which changes the DB/storage contract and makes the editor/runtime split inconsistent. Keep stripping the header in ParseScriptBody, but still prepend #!/bin/bash when persisting non-empty scripts.

Suggested fix
 func GenerateScript(body string) string {
 	body = strings.TrimSpace(body)
 	if body == "" {
 		return ""
 	}
-	return body + "\n"
+	return "#!/bin/bash\n" + body + "\n"
 }

As per coding guidelines, "Scripts must be stored with #!/bin/bash shebang; the editor shows/edits the body without the shebang".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// GenerateScript returns the script body trimmed of surrounding whitespace with a trailing newline.
// No shebang is prepended — the executor adds a platform-appropriate shebang at execution time.
func GenerateScript(body string) string {
body = strings.TrimSpace(body)
return scriptHeader + "\n\n" + body + "\n"
if body == "" {
return ""
}
return body + "\n"
// GenerateScript returns the script body trimmed of surrounding whitespace with a trailing newline.
// No shebang is prepended — the executor adds a platform-appropriate shebang at execution time.
func GenerateScript(body string) string {
body = strings.TrimSpace(body)
if body == "" {
return ""
}
return "#!/bin/bash\n" + body + "\n"
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@script.go` around lines 11 - 18, GenerateScript currently strips all
whitespace and returns only the body, breaking the storage contract that
persisted scripts must include the "#!/bin/bash" shebang; change GenerateScript
so that for non-empty bodies it trims whitespace but prepends the shebang line
("#!/bin/bash\n") before returning, while leaving ParseScriptBody responsible
for removing the shebang for the editor/runtime split; update GenerateScript to
return "" for empty bodies and "#!/bin/bash\n" + body + "\n" (or equivalent) for
non-empty scripts to preserve the persisted format.

loco1842 added 3 commits May 4, 2026 17:04
…ation

- ExecuteScript: pipe body to shell -lc stdin on Unix (no temp file
  needed, no shebang injection, no permission issues). Windows unchanged.
- stripShebang: return empty for shebang-only content (mirrors
  ParseScriptBody behavior), preventing execution of empty scripts.
- Move 5 test functions from script_test.go into db_test.go per repo
  convention.
-c requires a command string argument; piping via stdin needs -s (read from
stdin). Combined with -l for login shell behavior.
@loco1842
Copy link
Copy Markdown
Owner Author

loco1842 commented May 4, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 4, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@loco1842
Copy link
Copy Markdown
Owner Author

loco1842 commented May 4, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 4, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
executor.go (1)

30-42: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't bypass the configured Unix shell flag.

NewExecutor establishes -lc as the Unix execution contract, but ExecuteScript hardcodes -ls here. That makes this path diverge from the rest of the executor configuration and from the repository's Unix-shell requirement.

#!/bin/bash
set -euo pipefail

# Verify the configured Unix flag and the flag actually used by ExecuteScript.
rg -n --type=go 'flag = "-lc"|exec\.CommandContext\(ctx, e\.shell, "-ls"\)|exec\.CommandContext\(ctx, e\.shell, e\.flag' executor.go

As per coding guidelines, "Platform-aware shell execution must use $SHELL -lc on Unix (falls back to /bin/sh), cmd /C on Windows".

Also applies to: 125-126

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@executor.go` around lines 30 - 42, ExecuteScript is hardcoding the Unix shell
flag ("-ls") instead of using the configured e.flag set by NewExecutor, which
breaks the repo's requirement to use $SHELL -lc (or /bin/sh fallback) and cmd /C
on Windows; update all invocations of exec.CommandContext(ctx, e.shell, "-ls",
...) (and any other places where "-ls" is used) to pass e.flag (i.e.,
exec.CommandContext(ctx, e.shell, e.flag, ...)) so the executor consistently
uses the configured shell flag, and verify any other exec.CommandContext calls
in the file also use e.flag rather than hardcoded flags.
🧹 Nitpick comments (1)
db_test.go (1)

176-180: ⚡ Quick win

Add the no-trailing-newline shebang case.

This only covers #!/bin/bash\n. The regression fixed in stripShebang was #!/bin/bash with no newline, so this suite still won't catch that edge case.

Suggested test addition
 	// Only shebang, no body
 	result = ParseScriptBody("#!/bin/bash\n")
 	if result != "" {
 		t.Errorf("ParseScriptBody shebang only: got %q, want empty", result)
 	}
+
+	result = ParseScriptBody("#!/bin/bash")
+	if result != "" {
+		t.Errorf("ParseScriptBody shebang only without newline: got %q, want empty", result)
+	}
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@db_test.go` around lines 176 - 180, Add a test case in db_test.go to cover a
shebang with no trailing newline by calling ParseScriptBody with "#!/bin/bash"
(no "\n") and asserting the result is empty; update the existing test block that
checks shebang-only (the one using ParseScriptBody) to include this no-newline
variant so ParseScriptBody/stripShebang regression is exercised.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@executor.go`:
- Around line 214-225: OpenInTerminal contains its own inline shebang-stripping
logic that can still launch a shebang-only string as a command; replace that
logic by calling the existing stripShebang(content string) function (the same
one used by ExecuteScript) so shebang-only content becomes empty before
launching. Locate OpenInTerminal and any duplicate logic around lines ~229-236
and remove the inline trimming/HasPrefix/Index handling, call stripShebang on
the script content, and ensure the resulting string is checked for emptiness
exactly as ExecuteScript does before attempting to execute/open the terminal.

---

Duplicate comments:
In `@executor.go`:
- Around line 30-42: ExecuteScript is hardcoding the Unix shell flag ("-ls")
instead of using the configured e.flag set by NewExecutor, which breaks the
repo's requirement to use $SHELL -lc (or /bin/sh fallback) and cmd /C on
Windows; update all invocations of exec.CommandContext(ctx, e.shell, "-ls", ...)
(and any other places where "-ls" is used) to pass e.flag (i.e.,
exec.CommandContext(ctx, e.shell, e.flag, ...)) so the executor consistently
uses the configured shell flag, and verify any other exec.CommandContext calls
in the file also use e.flag rather than hardcoded flags.

---

Nitpick comments:
In `@db_test.go`:
- Around line 176-180: Add a test case in db_test.go to cover a shebang with no
trailing newline by calling ParseScriptBody with "#!/bin/bash" (no "\n") and
asserting the result is empty; update the existing test block that checks
shebang-only (the one using ParseScriptBody) to include this no-newline variant
so ParseScriptBody/stripShebang regression is exercised.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 9ba77355-a05a-451e-91e9-89d9829c207a

📥 Commits

Reviewing files that changed from the base of the PR and between 3ecd9fa and b3996ba.

📒 Files selected for processing (2)
  • db_test.go
  • executor.go

Comment thread executor.go
… add test

- OpenInTerminal: replace inline shebang stripping with stripShebang() call,
  fixing shebang-only edge case where raw #! would be passed to terminal.
- ExecuteScript: use e.flag consistently on all platforms. Unix path now
  writes temp file with #!/bin/sh shebang + chmod 0755 so -lc can exec it.
- db_test.go: add ParseScriptBody test for shebang with no trailing newline.
@loco1842
Copy link
Copy Markdown
Owner Author

loco1842 commented May 4, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 4, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@loco1842 loco1842 merged commit 9ec8b72 into main May 8, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant