Skip to content

fix(ssh): preserve git state on disconnect#2661

Open
janburzinski wants to merge 8 commits into
mainfrom
emdash/crash-report-ssh-git-refresh-z4a1n
Open

fix(ssh): preserve git state on disconnect#2661
janburzinski wants to merge 8 commits into
mainfrom
emdash/crash-report-ssh-git-refresh-z4a1n

Conversation

@janburzinski

Copy link
Copy Markdown
Collaborator

Description

this fixes two crashes i have had yesterday.

  • preserve last known git state when ssh refreshes fail temporarily
  • treat ssh disconnect/timeouts as recoverable git refresh errors
  • prevent failed background refreshes from emitting stale/empty updates
  • catch and log pty onExit handler errors instead of crashing
Checklist
  • I kept this PR small and focused
  • I ran a self-review before opening this PR
  • I ran the relevant local checks or explained why not
  • I updated docs when behavior or setup changed
  • I added or updated tests when behavior changed, or explained why not
  • I only added comments where the logic is not obvious
  • I used Conventional Commits for commit
    messages and, when possible, the PR title

@greptile-apps

greptile-apps Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes two crash scenarios on SSH disconnect by teaching the LiveModel-backed SSH git layer to distinguish recoverable transport errors (disconnects, timeouts) from real git failures. A new shared transport-errors.ts module is introduced so both git-service.ts and ssh-git.ts use the same predicate; a separate fix catches errors thrown inside PTY onExit handlers before they can propagate.

  • ssh-git.ts: computeRefs, computeRemotes, computeStatus, and computeHead now return Result<T, E> directly. On a recoverable SSH error they return err(error) instead of throwing, so LiveModel.scheduleBackground() routes the failure to onError (a log.warn) and keeps the last-good cached value rather than re-throwing into an unhandled microtask rejection.
  • git-service.ts: Recoverable SSH errors are re-thrown through previously silent .catch(() => ({ stdout: '' })) and empty-fallback catch blocks, so they reach computeStatus/computeHead rather than being swallowed as empty results.
  • local-pty.ts: onExit handler invocations are now wrapped in a try/catch that logs the error instead of propagating it through the node-pty callback chain.

Confidence Score: 5/5

Safe to merge — all changes are narrowly scoped to SSH error paths and the PTY exit callback, with no changes to happy-path logic.

The fix correctly exploits the LiveModel contract: returning err() from compute() keeps the cached value and routes the failure to onError instead of crashing. The git-service changes only affect previously-silent catch blocks that were already swallowing errors. The PTY change adds defensive error handling with no impact on normal exit flow. New tests cover the two background-refresh scenarios and the onExit handler case.

No files require special attention.

Important Files Changed

Filename Overview
apps/emdash-desktop/src/main/core/ssh/transport-errors.ts New shared utility that centralises recoverable SSH error detection via error codes and message regex; used by both git-service.ts and ssh-git.ts.
apps/emdash-desktop/src/main/core/runtime/legacy/ssh-git.ts compute() functions changed to return Result<T,E> directly; recoverable SSH errors now return err() so LiveModel keeps last-good state instead of crashing via unhandled microtask rejection.
apps/emdash-desktop/src/main/core/git/legacy/git-service.ts Recoverable SSH errors are re-thrown through previously-silent catch handlers (diff numstat, getHeadInfo, getRemotes) so they propagate up to the LiveModel boundary correctly.
apps/emdash-desktop/src/main/core/pty/local-pty.ts onExit handler invocations are now wrapped in try/catch; errors are logged rather than allowed to propagate through the node-pty callback chain.
apps/emdash-desktop/src/main/core/runtime/legacy/ssh-git.test.ts New test file; covers recoverable SSH error propagation through GitService, background refs refresh (LegacySshGitRepository), and background status refresh (LegacySshGitWorktree).
apps/emdash-desktop/src/main/core/pty/local-pty.test.ts New test case verifies that onExit handler errors are caught and logged rather than thrown.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Timer as Interval Timer
    participant Worktree as LegacySshGitWorktree
    participant LM as LiveModel
    participant CS as computeStatus()
    participant GS as GitService
    participant TE as isRecoverableSshTransportError

    Timer->>Worktree: pollStatus() invalidate()
    Worktree->>LM: scheduleBackground()
    LM->>CS: compute()
    CS->>GS: getFullStatus()
    GS-->>CS: throws SSH error
    CS->>TE: isRecoverableSshTransportError(error)
    TE-->>CS: true
    CS-->>LM: return err(error)
    Note over LM: completed=false, dirty=true
    LM->>LM: onError log.warn (no crash)
    Note over LM: cached value preserved, no subscriber update emitted
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Timer as Interval Timer
    participant Worktree as LegacySshGitWorktree
    participant LM as LiveModel
    participant CS as computeStatus()
    participant GS as GitService
    participant TE as isRecoverableSshTransportError

    Timer->>Worktree: pollStatus() invalidate()
    Worktree->>LM: scheduleBackground()
    LM->>CS: compute()
    CS->>GS: getFullStatus()
    GS-->>CS: throws SSH error
    CS->>TE: isRecoverableSshTransportError(error)
    TE-->>CS: true
    CS-->>LM: return err(error)
    Note over LM: completed=false, dirty=true
    LM->>LM: onError log.warn (no crash)
    Note over LM: cached value preserved, no subscriber update emitted
Loading

Reviews (2): Last reviewed commit: "refactor(ssh): share transport error rec..." | Re-trigger Greptile

Comment thread apps/emdash-desktop/src/main/core/git/legacy/git-service.ts Outdated
…sh-git-refresh-z4a1n

# Conflicts:
#	apps/emdash-desktop/src/main/core/pty/local-pty.test.ts
#	apps/emdash-desktop/src/main/core/pty/local-pty.ts
@janburzinski

Copy link
Copy Markdown
Collaborator Author

@greptileai

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