fix(daemon): resolve the shell in-process and cache successful resolution#5003
fix(daemon): resolve the shell in-process and cache successful resolution#5003mu-hashmi wants to merge 2 commits into
Conversation
Signed-off-by: Muhammad Hashmi <mhashmi@berkeley.edu>
|
View your CI Pipeline Execution ↗ for commit dac41ca
💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗ ☁️ Nx Cloud last updated this comment at |
There was a problem hiding this comment.
Pull request overview
This PR updates the daemon’s shell selection logic to avoid spawning a subprocess on hot paths by reading /etc/shells in-process and caching the first successful resolution for the daemon lifetime (while not caching failures to allow later retries).
Changes:
- Replaced
sh -c "grep ... /etc/shells"probing with in-process/etc/shellsparsing and preference-based selection. - Added mutex-guarded caching so successful shell resolution is computed once and reused across requests.
- Added unit tests covering preference order, comment filtering, env/missing-file fallback, and caching semantics.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| apps/daemon/pkg/common/get_shell.go | In-process /etc/shells parsing, fallback behavior, and mutex-guarded cache for resolved shell. |
| apps/daemon/pkg/common/get_shell_test.go | New unit tests validating shell resolution order, comment filtering, and caching/retry behavior. |
Comments suppressed due to low confidence (1)
apps/daemon/pkg/common/get_shell.go:77
os.LookupEnv("SHELL")returnsshellSet=trueeven when the value is an empty string. In that caseshellFallback()currently returns "", which can later causeexec.Command(shell)(e.g. inSpawnTTY) to fail. Treat empty$SHELLas unset and fall back tosh.
func shellFallback() string {
if shellEnv, shellSet := os.LookupEnv("SHELL"); shellSet {
return shellEnv
}
return "sh"
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
1 issue found across 2 files
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
Signed-off-by: Muhammad Hashmi <mhashmi@berkeley.edu>
2c15a7f to
b40f732
Compare
TL;DR: the daemon resolves the shell by reading
/etc/shellsin-process (no subprocess), caches a successful resolution for its lifetime, and can never return an empty shell.Description
Every toolbox exec, code-run, session create, and PTY spawn resolved the shell by spawning
sh -c \"grep '^[^#]' /etc/shells\"- an extra fork+execve on the daemon's five hottest call sites, and the first thing to fail under degraded filesystem conditions (during the fd-exhaustion incident the probe died before the real command, silently downgrading users tosh).Now
GetShell():/etc/shellsdirectly and preserves the exact preference order (/usr/bin/zsh>/bin/zsh>/usr/bin/bash>/bin/bash>$SHELLif non-empty >sh);sh) and retries next call;$SHELLset-but-empty falls through tosh(review-caught edge; regression-tested).Behavior note: a shell installed after daemon start is not picked up until restart (previously each exec re-probed). Accepted: daemon lifetime == sandbox session.
Review guide
apps/daemon/pkg/common/get_shell.go- the whole change.get_shell_test.go- 8 cases document the contract.Lowest-risk PR of the six; no generated files, no API surface.
Commit map
Related PRs
Independent. Siblings from the same incident: #5001, #5002.
Validation
gofmtclean,golangci-lintv2.6.2 zero issues.Closes #4997