Skip to content

fix(execd): parse dash/ash export -p and follow-up shell-fallback cleanups#1367

Open
Pangjiping wants to merge 1 commit into
mainfrom
fix/execd-shell-fallback-followups
Open

fix(execd): parse dash/ash export -p and follow-up shell-fallback cleanups#1367
Pangjiping wants to merge 1 commit into
mainfrom
fix/execd-shell-fallback-followups

Conversation

@Pangjiping

Copy link
Copy Markdown
Collaborator

Summary

Follow-ups from PR #1361 review, split into three logically independent commits:

  1. fix(execd): parse sh export -p output using dash/ash quote convention
    PR fix(execd): fall back to sh in bash and isolated sessions #1361 added a POSIX single-quoted branch to parseExportLine that only handled the '\'' escape emitted by bash / zsh / BSD sh, but dash and BusyBox ash — the default sh on Alpine / Debian slim images this fallback exists for — emit embedded single quotes as '"'"' instead. shellEscape also writes the '"'"' form, so on a bash-less image any session variable containing a single quote was silently corrupted on the next /session run.

    Accepted both escape forms in the parser and locked the invariant with:

    • New dash / BusyBox ash rows in TestParseExportLine_BashAndShFormats
    • New TestShellEscapeParseExportLine_RoundTrip fuzz-style round-trip
    • New TestBashSession_FallsBackToSh_PersistsSingleQuotedValue end-to-end
  2. refactor(execd): unify shell launch via shellCommand helper and cache getShell
    The five shell-launch sites in pkg/runtime were each hand-rolling the same "preferred shell + optional --noprofile --norc" logic, with drift in flag ordering. Extract shellCommand() so bash-session, PTY, and isolated-session launches share the same code path. The two Controller.runCommand* sites keep their inline getShell + explicit -c invocation because --noprofile/--norc are no-ops for one-shot bash -c.

    Cache getShell() with sync.Once — execd's PATH is fixed at startup, so re-running exec.LookPath on every request is pure overhead. resetShellCacheForTest is exposed for tests that mutate PATH (wired into useShOnlyPath).

  3. test(execd): replace inline bash lookups with requireBash helper
    25+ tests inlined the same exec.LookPath("bash"); err != nil { t.Skip } guard with two divergent skip messages. Extract requireBash(t) (in both pkg/runtime and pkg/web/controller) so future bash-dependent tests can opt in with one line and a consistent reason.

Each commit builds and passes tests standalone (bisect-safe).

Testing

  • Not run (explain why)
  • Unit tests
  • Integration tests
  • e2e / manual verification

Passed on darwin/arm64:

  • cd components/execd && go build ./...
  • cd components/execd && go vet ./pkg/runtime ./pkg/web/controller
  • cd components/execd && go test ./pkg/runtime ./pkg/web/controller -count=1
  • cd components/execd && go test ./pkg/runtime -run 'TestParseExportLine|TestShellEscape|TestBashSession_FallsBackToSh|TestPTYSession_FallsBackToSh|TestIsolatedSession_FallsBackToSh' -count=10
  • Reverse-verified the parser fix: temporarily reverting only the parseExportLine hunk while keeping the new tests reproduces the failure on the dash/ash quote-escape cases, confirming the tests actually gate the fix.

Breaking Changes

  • None
  • Yes (describe impact and migration path)

No public API, config, CRD, or CLI surface changes. Internal refactor only.

Checklist

@github-actions github-actions Bot added component/execd size/M Denotes a PR that changes 30-99 lines, ignoring generated files. labels Jul 22, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3beb82408b

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread components/execd/pkg/runtime/command_test.go
@Pangjiping
Pangjiping force-pushed the fix/execd-shell-fallback-followups branch from 3beb824 to e7ba57c Compare July 22, 2026 02:47

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e7ba57c9d9

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread components/execd/pkg/runtime/bash_session.go Outdated
@Pangjiping
Pangjiping force-pushed the fix/execd-shell-fallback-followups branch from e7ba57c to 5ef302a Compare July 22, 2026 03:02
@github-actions github-actions Bot added size/L Denotes a PR that changes 100-499 lines, ignoring generated files. and removed size/M Denotes a PR that changes 30-99 lines, ignoring generated files. labels Jul 22, 2026
Follow-ups from PR #1361 review.

Parser fix (main change)

PR #1361 added a POSIX single-quoted branch to parseExportLine that
only handled the '\'' escape emitted by bash / zsh / BSD sh and
assumed the value was wrapped in a matching pair of '...'. Neither
assumption holds under dash / BusyBox ash — the default sh on Alpine
and Debian slim images this fallback exists for:

  - dash writes embedded single quotes as '"'"' rather than '\''.
  - dash does not wrap the whole value in a single pair of quotes
    when the value starts or ends with a single quote. For example
    trailing' becomes 'trailing'"'"' — not '-terminated.

shellEscape writes the '"'"' form as well, so any session variable
containing a single quote was silently corrupted on the next /session
run whenever bash was unavailable.

Replace the strip-and-replace approach with unquoteShellWord, a proper
POSIX shell-word unquoter that walks the raw string and consumes
'...', "...", and \\c segments in order.

Tests added to lock in the wire format:
  - TestParseExportLine_BashAndShFormats table rows covering
    trailing, leading, both-side, single-', and multi-quote runs.
  - TestParseExportLine_DashFormatRoundTrip driven by dashExportEscape,
    a helper that mirrors /bin/dash 0.5.x export -p output (verified
    against real dash).
  - TestShellEscapeParseExportLine_RoundTrip expanded with more edges.
  - TestBashSession_FallsBackToSh_PersistsSingleQuotedValue exercises
    the full write-then-read cycle under sh.

Reverse-verified: reverting only bash_session.go while keeping the new
tests reproduces the corruption, confirming the tests actually gate
the fix.

Shell-launch consolidation

The five shell-launch sites in pkg/runtime each hand-rolled the same
'preferred shell + optional --noprofile --norc' logic, with drift in
flag ordering. Extract shellCommand() so bash-session, PTY, and
isolated-session launches share the same code path. The two
Controller.runCommand* sites keep their inline getShell + explicit
'-c' invocation because --noprofile/--norc are no-ops for one-shot
'bash -c'.

Cache getShell() with sync.Once — execd's PATH is fixed at startup,
so re-running exec.LookPath on every request is pure overhead. Expose
resetShellCacheForTest for tests that mutate PATH, wired into
useShOnlyPath.

Test helper consolidation

25+ tests inlined the same 'exec.LookPath("bash"); err != nil { t.Skip }'
guard, with two divergent skip messages. Extract requireBash(t) into
both pkg/runtime and pkg/web/controller. The helper lives in a
testhelpers_test.go without a !windows build tag so command_test.go —
which is compiled on Windows — can reference it; resetShellCacheForTest
and useShOnlyPath remain in shell_fallback_test.go (!windows) where
their symlink-based setup applies.
@Pangjiping
Pangjiping force-pushed the fix/execd-shell-fallback-followups branch from 5ef302a to 7295518 Compare July 22, 2026 03:15
@Pangjiping

Copy link
Copy Markdown
Collaborator Author

@bcho ready for review. 😊

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 72955188d4

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread components/execd/pkg/runtime/bash_session.go
@Pangjiping Pangjiping added the bug Something isn't working label Jul 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working component/execd size/L Denotes a PR that changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants