Skip to content

fix(host-service): don't crash startup when shell env probe fails#4150

Merged
saddlepaddle merged 1 commit into
mainfrom
fix-host-service-env-pars
May 6, 2026
Merged

fix(host-service): don't crash startup when shell env probe fails#4150
saddlepaddle merged 1 commit into
mainfrom
fix-host-service-env-pars

Conversation

@saddlepaddle
Copy link
Copy Markdown
Collaborator

@saddlepaddle saddlepaddle commented May 6, 2026

Summary

User report (john@benomadic.co): can open v1 workspaces but every v2 attempt errors at startup with

[host-service] Failed to start: Error: Failed to parse shell env output - delimiter not found
    at parseEnvOutput (.../host-service.js:27624:11)

v1 desktop main has had a process.env fallback in apps/desktop/src/lib/.../shell-env.ts:76-95 since launch — when its login-shell probe fails, it logs a warning and keeps going. v2 host-service uses its own probe in packages/host-service/src/terminal/clean-shell-env.ts and any failure propagates to process.exit(1) in apps/desktop/src/main/host-service/index.ts. That asymmetry is the v1-works/v2-doesn't symptom.

Changes

  • terminal/env.ts:resolveTerminalBaseEnv — try/catch around getStrictShellEnvironment; on failure, log a warning with the underlying error and fall back to a process.env snapshot + augmentPathForMacOS. Same shape as v1.
  • terminal/clean-shell-env.ts — pin the snapshot shell's cwd to $HOME (backport of 4feaa3d0, issue [bug] the title of bug report #4025; the brew "current working directory must be readable" failure mode), and include truncated stdout/stderr in the thrown error so the next user report surfaces which shell behaviour broke instead of a blind "delimiter not found".
  • Export augmentPathForMacOS from clean-shell-env.ts so the fallback path can reuse it.

exec-gh already wraps getStrictShellEnvironment().catch(...), so making the strict probe throw richer errors doesn't regress that consumer.

Test plan

  • bun run lint clean
  • tsc --noEmit clean (host-service)
  • bun test src/terminal/ — 37 pass, 0 fail
  • TestFlight build to john; confirm host-service starts and capture the new [host-service] Shell env snapshot failed, falling back to process.env: <details> line so we can see his actual shell output

Summary by cubic

Stop host-service from crashing at startup when the shell env probe fails by falling back to a process.env snapshot (same as v1). Also improves diagnostics and stability of the shell probe.

  • Bug Fixes
    • In packages/host-service/src/terminal/env.ts, wrap getStrictShellEnvironment with try/catch; on failure log a warning and fall back to process.env + augmentPathForMacOS (now exported).
    • In packages/host-service/src/terminal/clean-shell-env.ts, run the probe shell with cwd set to $HOME to avoid unreadable-cwd issues (e.g., brew).
    • Include truncated stdout/stderr in thrown errors to make probe failures diagnosable.

Written for commit 4641abe. Summary will update on new commits.

Summary by CodeRabbit

  • Bug Fixes
    • Terminal environment resolution now includes fallback mechanism to ensure continued functionality if shell initialization fails.
    • Error messages now provide comprehensive diagnostics (stdout and stderr) while being truncated for improved readability.

Mirrors v1's posture from apps/desktop shell-env.ts: when the login-shell
snapshot fails (parse error, non-zero exit, exotic shell, rc files that
redirect stdout), log a warning and fall back to process.env +
augmentPathForMacOS instead of bringing down host-service startup.

Reported by a user who could open v1 workspaces but never v2 — desktop
main logged "Failed to start: Failed to parse shell env output -
delimiter not found" and process.exit(1)'d before the HTTP server came
up. v1 silently falls back via shellEnv()'s catch, which is why only v2
broke.

Also pin the snapshot shell's cwd to \$HOME (backport of 4feaa3d,
issue #4025) and include captured stdout/stderr in the thrown error so
the next failure surfaces a diagnosable signal instead of a blind
"delimiter not found".
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 6, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a8748287-375f-463e-9cb2-534e9a30ca82

📥 Commits

Reviewing files that changed from the base of the PR and between a07e446 and 4641abe.

📒 Files selected for processing (2)
  • packages/host-service/src/terminal/clean-shell-env.ts
  • packages/host-service/src/terminal/env.ts

📝 Walkthrough

Walkthrough

This PR improves resilience and diagnostics in host-service shell environment handling. It exports a macOS PATH augmentation utility, adds diagnostic truncation to bound error output size, refactors error construction to include truncated stdout/stderr, and adds try/catch fallback in environment resolution to gracefully degrade to process.env when shell derivation fails.

Changes

Shell Environment Resilience & Diagnostics

Layer / File(s) Summary
Public API
packages/host-service/src/terminal/clean-shell-env.ts
augmentPathForMacOS function is exported to enable reuse by other modules.
Diagnostic Helpers
packages/host-service/src/terminal/clean-shell-env.ts
New truncateForDiagnostics helper limits diagnostic output to a fixed length, appending ellipsis when exceeded.
Error Construction
packages/host-service/src/terminal/clean-shell-env.ts
spawnCleanShellEnv refactored to compute stdout once and include truncated stdout/stderr in both exit-code and parse-failure error messages for bounded diagnostics.
Fallback Mechanism
packages/host-service/src/terminal/env.ts
resolveTerminalBaseEnv() now wraps getStrictShellEnvironment() in try/catch; on failure, logs warning, snapshots process.env, applies macOS PATH augmentation, and returns fallback to prevent startup crashes.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

A rabbit hops through shells so bright,
With fallback paths now shining light,
When logins fail, we gracefully bend,
Process.env becomes our friend,
Diagnostics trimmed, no endless spew,
Resilience blooms in all we do! 🐰✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title concisely and accurately describes the main change: preventing host-service crashes when shell environment probing fails, which aligns with the core fix in the changeset.
Description check ✅ Passed The description comprehensively covers the problem, changes made, related issues, type of change, testing steps, and additional context. All required sections are present and well-documented.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-host-service-env-pars

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

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

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 6, 2026

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ✅ Neon database branch

Thank you for your contribution! 🎉

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 6, 2026

Greptile Summary

This PR fixes a v2-only startup crash where a failed login-shell environment probe (e.g. brew aborting due to an unreadable cwd) propagated to process.exit(1), while v1 silently fell back to process.env. The fix brings v2 to parity with v1's resilience.

  • env.ts: resolveTerminalBaseEnv now wraps getStrictShellEnvironment in a try/catch, logs a warning, and returns a process.env snapshot augmented with common macOS paths on failure — identical in shape to the v1 apps/desktop fallback.
  • clean-shell-env.ts: The spawned snapshot shell is now anchored to $HOME as its cwd (backport of [bug] the title of bug report #4025, prevents brew and similar rc-file tools from aborting on an unreadable working directory), and error messages now include truncated stdout/stderr so future failure reports identify the specific shell behaviour that broke instead of showing a blind "delimiter not found".

Confidence Score: 4/5

Safe to merge; the fallback path is narrow, well-guarded, and mirrors established v1 behaviour.

The two changes are independent and targeted: the cwd anchor addresses a known shell-tool abort pattern, and the try/catch fallback prevents a hard startup failure. The only gap found is that when a shell is killed by a signal before writing the delimiters, the signal value is available in scope but not included in the parse-error message, making that specific failure mode slightly harder to diagnose from logs.

No files require special attention; clean-shell-env.ts has the minor diagnostic gap noted in the inline comment.

Important Files Changed

Filename Overview
packages/host-service/src/terminal/clean-shell-env.ts Adds HOME-anchored cwd to spawned shell (fixes brew #4025), enriches thrown errors with truncated stdout/stderr for diagnostics, and exports augmentPathForMacOS for the new fallback path. Logic is sound; one minor gap in signal reporting on the parse-failure branch.
packages/host-service/src/terminal/env.ts Wraps getStrictShellEnvironment() in try/catch and falls back to a process.env snapshot + augmentPathForMacOS on failure; mirrors the v1 shell-env.ts fallback pattern and fixes the startup crash. Clean change.

Sequence Diagram

sequenceDiagram
    participant HS as host-service startup
    participant RTE as resolveTerminalBaseEnv
    participant GSE as getStrictShellEnvironment
    participant SH as login shell

    HS->>RTE: "await resolveTerminalBaseEnv()"
    RTE->>GSE: "await getStrictShellEnvironment()"
    GSE->>SH: "spawn(shell, [-i,-l,-c,...], cwd=HOME)"

    alt probe succeeds
        SH-->>GSE: "stdout with delimiters"
        GSE-->>RTE: "parsed env Record"
        RTE-->>HS: "shell-derived env"
    else probe fails
        SH-->>GSE: "error (enriched with stdout/stderr)"
        GSE-->>RTE: "throws Error"
        RTE->>RTE: "catch: warn + snapshotStringEnv(process.env) + augmentPathForMacOS"
        RTE-->>HS: "process.env fallback"
    end
Loading
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
packages/host-service/src/terminal/clean-shell-env.ts:174-186
**Signal not surfaced in parse-error path**

When a shell is killed by a signal (e.g. an external `SIGTERM` arrives before the delimiters are written), `code` is `null` so the non-zero-exit branch is skipped, and the subsequent parse error message is built without mentioning `signal`. The user sees something like `Failed to parse shell env output - delimiter not found (shell=/bin/zsh stdout= stderr=)` with no indication the process was terminated by a signal. Since `signal` is already in scope in the `close` callback, appending `(signal ? \` signal=${signal}\` : "")` to the error string in the `catch` block would surface it, matching the treatment in the non-zero-exit branch above.

Reviews (1): Last reviewed commit: "fix(host-service): don't crash startup w..." | Re-trigger Greptile

Comment on lines 174 to 186
try {
resolve(parseEnvOutput(Buffer.concat(stdoutBuffers).toString("utf8")));
resolve(parseEnvOutput(stdout));
} catch (error) {
reject(error);
const detail = error instanceof Error ? error.message : String(error);
reject(
new Error(
`${detail} (shell=${shell}` +
` stdout=${truncateForDiagnostics(stdout)}` +
(stderr ? ` stderr=${truncateForDiagnostics(stderr)}` : "") +
")",
),
);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Signal not surfaced in parse-error path

When a shell is killed by a signal (e.g. an external SIGTERM arrives before the delimiters are written), code is null so the non-zero-exit branch is skipped, and the subsequent parse error message is built without mentioning signal. The user sees something like Failed to parse shell env output - delimiter not found (shell=/bin/zsh stdout= stderr=) with no indication the process was terminated by a signal. Since signal is already in scope in the close callback, appending (signal ? \ signal=${signal}` : "")to the error string in thecatch` block would surface it, matching the treatment in the non-zero-exit branch above.

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/host-service/src/terminal/clean-shell-env.ts
Line: 174-186

Comment:
**Signal not surfaced in parse-error path**

When a shell is killed by a signal (e.g. an external `SIGTERM` arrives before the delimiters are written), `code` is `null` so the non-zero-exit branch is skipped, and the subsequent parse error message is built without mentioning `signal`. The user sees something like `Failed to parse shell env output - delimiter not found (shell=/bin/zsh stdout= stderr=)` with no indication the process was terminated by a signal. Since `signal` is already in scope in the `close` callback, appending `(signal ? \` signal=${signal}\` : "")` to the error string in the `catch` block would surface it, matching the treatment in the non-zero-exit branch above.

How can I resolve this? If you propose a fix, please make it concise.

@saddlepaddle saddlepaddle merged commit 9ddacf9 into main May 6, 2026
17 checks passed
saddlepaddle added a commit that referenced this pull request May 7, 2026
Changes since v0.2.10:

- host-service: gh CLI is first-class for PR/issue search; failures
  surface in the UI instead of silently degrading. (#4140)
- host-service: respawn lost terminal sessions on attach instead of
  dead-ending the pane after laptop sleep / daemon restart. (#4149)
- host-service: don't crash startup when the shell env probe fails
  (e.g. user's .zshrc errors). (#4150)

Also drop --draft from the CLI release workflow so the per-version
release auto-publishes alongside the cli-latest rolling pointer.
--prerelease stays (still required to keep desktop's auto-updater
from picking up CLI releases on /releases/latest/).

Push cli-v0.2.11 after this lands to fire the release pipeline.
@saddlepaddle saddlepaddle mentioned this pull request May 7, 2026
4 tasks
saddlepaddle added a commit that referenced this pull request May 7, 2026
Changes since v0.2.10:

- host-service: gh CLI is first-class for PR/issue search; failures
  surface in the UI instead of silently degrading. (#4140)
- host-service: respawn lost terminal sessions on attach instead of
  dead-ending the pane after laptop sleep / daemon restart. (#4149)
- host-service: don't crash startup when the shell env probe fails
  (e.g. user's .zshrc errors). (#4150)

Also drop --draft from the CLI release workflow so the per-version
release auto-publishes alongside the cli-latest rolling pointer.
--prerelease stays (still required to keep desktop's auto-updater
from picking up CLI releases on /releases/latest/).

Push cli-v0.2.11 after this lands to fire the release pipeline.
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