Files
orca/docs/source-control-push-failure-ai-recovery.md
Jinjinganddalveytech-vincent 070a956a72 feat(source-control): add push failure AI recovery (#7826)
* feat(source-control): add Fix push failure with AI for pre-push hooks

Detect pre-push hook failures separately from auth/transport errors so
push toasts and inline messages no longer suggest checking repo access.
Mirror the commit-failure recovery flow with a fixPushFailure action,
summary panel, details dialog, and agent launch recipe in Settings.

Fixes #6497

* feat(source-control): add push failure AI recovery

Co-authored-by: dalveytech-vincent <vincent@dalveytech.com>

---------

Co-authored-by: dalveytech-vincent <vincent@dalveytech.com>
2026-07-08 18:59:28 -07:00

369 lines
20 KiB
Markdown

# Source Control Push Failure AI Recovery
## Problem
Push, force-push, publish, and sync push-stage failures flow through the shared
remote operation formatter in `src/renderer/src/lib/source-control-remote-error.ts:94`
and the editor-store toast paths in `src/renderer/src/store/slices/editor.ts:3716`
and `src/renderer/src/store/slices/editor.ts:3821`. A local pre-push hook or
lint hook is not an auth, protected-branch, or transport problem, so generic
remote guidance sends the user in the wrong direction.
PR #7787 proves the behavior is useful, but the prototype needs tightening before
it is maintainable:
- `src/renderer/src/components/right-sidebar/SourceControl.tsx:1938` and
`src/renderer/src/components/right-sidebar/SourceControl.tsx:5799` repeat
push-failure predicates and feed live branch/file state into a prompt that says
"at failure time".
- `src/renderer/src/components/right-sidebar/use-source-control-recovery-ai.ts:63`
imports push detection again instead of consuming one derived recovery model.
- `src/renderer/src/components/right-sidebar/source-control-ai-commit-failure-launch.ts:24`
and `src/renderer/src/components/right-sidebar/source-control-ai-push-failure-launch.ts:24`
are copied launch flows with action-specific copy.
- `src/shared/source-control-commit-failure-agent-command.ts:6` and
`src/shared/source-control-push-failure-agent-command.ts:6` are identical
command-template builders.
- `src/renderer/src/components/right-sidebar/source-control-fix-split-button.tsx:21`
already contains a reusable split button, while `SourceControl.tsx:6415`
carries a second inline version.
- `src/renderer/src/lib/source-control-remote-error.ts:148` currently treats
any `isSync` error as push-like for hook detection. `syncBranch` calls the
formatter for fetch, upstream-status, pull, and push failures, so the renderer
cannot prove a sync error came from the push stage without an explicit marker.
- `src/renderer/src/components/right-sidebar/SourceControl.push-failure-recovery.test.ts:2`
imports prompt helpers through the large React module instead of shared prompt
modules.
## Goal
Add first-class recovery for pre-push hook failures without growing a parallel
push-only subsystem:
1. Toasts for push, force-push, publish, and sync push-stage failures say
"blocked" for detected pre-push or lint-hook output.
2. Source Control shows a concise Push blocked panel with Details and AI Fix
only for a failure snapshot that is both push-like and hook-like.
3. AI Fix launches the configured `fixPushFailure` Source Control action with a
safe, provider-neutral prompt.
4. Commit-failure recovery and push-failure recovery share launch,
command-template, split-button, and dense error-panel primitives.
## Non-goals
- Do not fix server-side pre-receive hooks, hosted CI failures, or provider-side
protected-branch failures from this entry point.
- Do not bypass hooks, add `--no-verify`, push from the launched agent, create a
PR, or assume GitHub-specific terminology.
- Do not add app-wide persistence for this transient failure panel.
- Do not redesign the Source Control panel or the Source Control AI settings
model.
- Do not fold unrelated launch surfaces such as checks recovery into this change
unless a tiny shared helper is already needed for commit/push recovery.
## Design
1. Keep push-hook detection and prompt building in shared code.
- `src/shared/source-control-push-failure.ts` owns normalization, bounded
scanning, summary text, Details eligibility, prompt truncation, prompt
rules, and changed-file list bounding.
- Detection must stay conservative: explicit `pre-push`/`prepush`,
`hook declined to push`, hook-runner output in push context, or lint output
in push context. Auth, non-fast-forward, protected branch, pre-receive,
submodule, and generic transport failures stay on the existing remote-error
path.
- Keep the output scan bounded at 64 KiB and the prompt failure-output
section bounded. Also cap prompt file lines to a small constant with an
omitted-count line; do not rely on the git-status cap, which is far too high
for prompt context.
2. Make remote-error formatting sync-stage aware.
- Extend `RemoteOperationErrorOptions` with an explicit push-stage flag for
sync, for example `isSyncPushStage`.
- Gate push-hook classification on `isPush`, `isForcePush`, `publish`, or
`isSyncPushStage`; do not use bare `isSync` as evidence of a push failure.
- In `syncBranch`, pass the push-stage flag only inside the two inner
`pushRuntimeGit` catch blocks. The outer fetch, upstream-status, and pull
catches still pass `isSync` for Sync-shaped generic copy but must not render
blocked hook copy.
- Carry the same push-stage fact back to the renderer, either by tagging the
thrown `Error` with a narrow exported marker or by wrapping it in a typed
error that preserves the original message/cause. `SourceControl` must set
`syncPushStage` only from that marker, not from `kind === 'sync'` plus
hook-like stderr.
- Keep submodule push messages before hook detection so submodule guidance
remains more specific than AI recovery.
3. Replace one-off command builders with one shared recovery command builder.
- Add a shared builder that accepts `{ actionId, promptOverride,
commandInputTemplate, basePrompt }` and renders the existing Source Control
AI command template for launch actions.
- Keep compatibility exports for commit and push helpers if nearby tests or
call sites still use those names, but make them wrappers over the generic
builder.
- Move prompt-builder tests to import from shared modules, not
`SourceControl.tsx`.
4. Capture and derive push recovery state once.
- Extend `SourceControlActionError` with the raw error, a push-stage marker
for sync failures, the branch name at failure time, a bounded status-entry
snapshot at failure time, and a per-worktree sequence token.
- `runRemoteAction` clears the worktree's previous error and increments its
sequence before starting. A catch may write an error only if its sequence
still owns that worktree, so a slow failure cannot overwrite a newer retry
or success.
- Add a focused renderer helper that accepts the captured
`SourceControlActionError` and the current branch name, then returns either
`null` or a model with raw/sanitized detail text, summary, details flag,
kind label, and AI prompt.
- The helper returns `null` unless the operation is `push`, `force_push`,
`publish`, or `sync` with the sync push-stage marker and
`isPushHookFailure(rawError)` passes.
- If the current branch is known and differs from the captured branch, hide or
clear the model instead of launching an agent with wrong branch context.
- Pass that single model to `useSourceControlAi` and `CommitArea`; neither
prop construction nor `CommitArea` should repeat the push-hook predicate.
5. Use one generic recovery launcher for commit and push failures.
- Replace the separate commit and push launch files with
`source-control-ai-recovery-launch.ts`, or keep the old files as one-line
wrappers if import stability is cheaper.
- The generic launcher owns connection resolution, SSH/local agent discovery,
saved-agent validation, agent-args validation, agent selection, terminal
launch, focus, and success/failure toasts.
- Resolve connection without collapsing local and unresolved states: read
`const worktreeConnectionId = getConnectionId(worktreeId)`, use it when it
is a string or `null`, and only fall back to `sourceRepoConnectionId` when
the worktree lookup returns `undefined`. If the fallback is also
`undefined`, show the workspace-connection error instead of launching
locally. `null` means proven local; a string means SSH/remote.
- Validate saved CLI args before agent detection or terminal creation. On
Windows, keep using PowerShell-safe planning through
`planAgentCliArgsSuffix(..., 'powershell')`.
- Action-specific data is limited to action id, base prompt, empty-prompt
copy, unavailable-agent copy, and success copy.
6. Keep the recovery hook thin.
- `use-source-control-recovery-ai.ts` builds the commit prompt, accepts the
already-derived push recovery prompt/model, keeps independent loading
flags, and calls the generic launcher.
- It should not import push-hook detection, resolve agents, or duplicate
launch plumbing.
7. Extract and reuse the recovery UI.
- Reuse or replace the existing
`src/renderer/src/components/right-sidebar/source-control-fix-split-button.tsx`
rather than adding another split-button component.
- Move the dense recovery notice and Details dialog into focused renderer
modules named for source-control recovery. Avoid `helpers`, `utils`, and new
max-lines suppressions.
- Reuse the same notice/dialog component for commit and push recovery with
action-specific labels, summary, details, prompt, saved recipe, and launch
callback.
- Keep normal remote errors filtered out when a push recovery model is
rendered, so the user does not see both Push blocked and generic remote
error copy for the same failure.
8. Retain `fixPushFailure` in the existing Source Control AI action model.
- The prototype already adds `fixPushFailure` to
`src/shared/source-control-ai-actions.ts`; keep it as a launch action like
`fixCommitFailure`.
- Verify default settings, normalizers, global settings rows, repository
override rows, labels, descriptions, and variable chips all include Push
failure fixes.
- Do not create a push-specific settings store or migration. Existing
`normalizeSourceControlAiSettings` hydrates missing action defaults.
## Data Flow
- User clicks Push, Force Push, Publish Branch, or Sync.
- `runRemoteAction` clears that worktree's previous remote error, records a new
sequence token, and starts the editor-store remote action.
- Git push fails and throws.
- `pushBranch` or sync's inner push-stage catch calls
`resolveRemoteOperationErrorMessage` with push-like options. Sync fetch, pull,
and upstream-status failures do not pass the push-stage flag.
- Sync's inner push-stage catch marks the rethrown error as push-stage so
`runRemoteAction` can preserve `syncPushStage` in the captured snapshot.
- The formatter classifies hook output before auth/transport fallbacks only for
push-like options and shows blocked toast copy.
- `SourceControl` catches the same error, and if the sequence still owns the
worktree, stores `{ kind, rawError, message, syncPushStage, branchName,
entriesSnapshot }`.
- The push recovery helper derives one `pushRecovery` model from that snapshot.
- `CommitArea` renders normal remote errors or the Push blocked recovery notice.
- AI Fix builds the `fixPushFailure` command input and launches the selected
agent in the owning local or SSH runtime.
## Edge Cases
- Auth, missing repo, protected-branch, pre-receive, non-fast-forward, and
transport failures must not show the Push blocked panel.
- Submodule push errors keep their existing specialized messages before
push-hook detection.
- Sync failures from fetch, upstream-status, or pull stages must not show Push
blocked, even if stderr contains words like "lint" or "hook".
- Create PR intent remote failures use the same `runRemoteAction` plumbing; if
CommitArea is not visible, blocked toast copy is still required but no hidden
panel work is needed.
- A newer remote operation for the same worktree must clear stale recovery state
immediately and prevent an older in-flight failure from writing after it.
- Switching worktrees or branches must not show another worktree or branch's
push failure.
- External edits or another Orca window may make the failure stale. Do not add
cross-window persistence for this feature; rely on local retry/remount/branch
mismatch clearing and list this as residual risk.
- Source Control AI hidden for a repo still shows the blocked notice and Details;
it hides AI Fix.
- SSH worktrees must detect and launch agents on the owning connection, not a
local fallback.
- Windows launch planning must continue to use PowerShell-safe agent args.
- Very large hook output and very large changed-file sets must be bounded before
prompt generation.
- ANSI/control output must not leak into summaries or Details comparison logic.
Details may show sanitized text while preserving enough line breaks for
debugging.
- Empty custom command templates must stay empty so the launcher rejects them
with a clear settings error.
- Prompt text must treat file paths, branch names, and hook output as data, not
instructions.
## Test Plan
- Shared unit tests:
- `src/shared/source-control-push-failure.test.ts` covers detection positives,
auth/protected/pre-receive/non-fast-forward negatives, ANSI/control
stripping, bounded scanning, details comparison, prompt output truncation,
prompt file-list capping, and provider-neutral prompt rules.
- Shared command-template tests cover the generic recovery builder plus
commit/push wrappers, including empty templates and prompt overrides.
- `src/shared/source-control-ai-actions.test.ts` covers `fixPushFailure`
label, default template, variables, normalization, and action-list inclusion.
- Remote formatter and store tests:
- `src/renderer/src/lib/source-control-remote-error.test.ts` covers push,
force-push, publish, and sync push-stage blocked copy plus sync non-push
stage negatives, auth/non-fast-forward/protected/pre-receive/submodule
regressions.
- `src/renderer/src/store/slices/editor.test.ts` covers the `pushBranch` toast
path and both sync push-stage and sync non-push-stage toasts.
- Renderer model and UI tests:
- A focused push recovery derivation test covers operation kind, sync
push-stage marker, branch mismatch, stale sequence ownership, snapshot file
entries, summary, details flag, prompt contents, and ordinary remote errors.
- `CommitArea` tests cover the Push blocked notice, Details dialog, AI Fix
visible/hidden, ordinary remote errors, no duplicate remote error when a push
model exists, and unchanged commit-failure recovery rendering.
- Settings tests cover global and repository Source Control AI action rows
showing Push failure fixes.
- Launcher tests:
- Generic recovery launcher tests cover commit and push action ids, invalid CLI
args before detection, empty template rejection, unavailable saved agent,
local versus SSH agent detection, successful launch/focus, and success copy.
- Checks:
- Run targeted vitest files above, `pnpm typecheck`, `pnpm lint`, and
`pnpm check:max-lines-ratchet`.
## UI Quality Bar
The Source Control recovery UI must match `docs/STYLEGUIDE.md`: monochrome,
quiet, dense, token-based, and consistent with adjacent commit-failure recovery.
Use existing shadcn `Button`, `DropdownMenu`, and `Dialog` primitives, lucide
icons, `card`/`border`/`destructive`/`muted` tokens, and the existing action
recipe row patterns. The Push blocked notice should keep the summary readable at
narrow right-sidebar widths, avoid nested cards, avoid new color values, show
stable split-button geometry while launching, and keep Details as progressive
disclosure for long hook output. Dialogs and dropdowns must remain usable in
light/dark mode and under SSH latency.
## Review Screenshots
1. Source Control after a simulated pre-push hook failure: Push blocked notice
with summary, AI Fix, and Details.
2. Push failure Details dialog showing hook output and Fix with AI.
3. AI Fix customize-launch dialog for `fixPushFailure`.
4. Ordinary push auth/protected/remote failure state: regular remote error, no
Push blocked notice.
5. Sync fetch/pull-stage failure state: Sync-shaped remote error, no Push
blocked notice.
6. Source Control AI hidden for the repo: Push blocked notice and Details, no AI
Fix.
7. Source Control AI settings showing the Push failure fixes launch recipe.
8. Adjacent commit-failure recovery notice still rendering correctly after the
shared UI extraction.
## Rollout
1. Tighten shared push-failure classifier/prompt bounds and add the generic
recovery command builder.
2. Make remote formatter/store sync-stage aware and update toast tests.
3. Add the `SourceControlActionError` snapshot/sequence data and one push
recovery derivation helper.
4. Replace duplicated commit/push launchers with the generic recovery launcher
and thin hook wiring.
5. Extract/reuse source-control recovery UI components, including the existing
split-button module.
6. Verify `fixPushFailure` settings/default/normalizer coverage and i18n labels.
7. Update targeted tests and run typecheck, lint, and max-lines ratchet.
8. Validate rendered Electron states with screenshots before opening the PR.
## Lightweight Eng Review
- Scope: Reduced from PR #7787's push-specific parallel path to a shared
recovery layer. Keep the feature limited to local pre-push/lint-hook detection,
blocked copy, an inline recovery notice, Details, and AI launch. No persistence
or provider-specific recovery.
- Architecture/data flow: Shared code owns classification, prompt text, and
command-template rendering; the editor store owns stage-aware toast formatting;
`SourceControl` owns transient worktree-scoped failure snapshots and sequence
invalidation; the generic launcher owns local/SSH agent discovery and terminal
launch.
- Failure modes covered:
- Sync stage ambiguity: only the inner push stage may classify as hook-blocked.
- Auth/protected/pre-receive/non-fast-forward/submodule/transport errors stay
out of push recovery.
- Branch or worktree switches suppress stale recovery models.
- Per-worktree sequence tokens prevent older in-flight failures from
overwriting newer retries or successes.
- Empty custom templates, invalid CLI args, unavailable saved agents, missing
worktree context, SSH host detection, and Windows shell planning fail before
terminal launch.
- Large or hostile hook output and file lists are bounded and treated as data
in prompts.
- Test coverage required:
- Shared classifier/prompt/command/action-model tests in `src/shared`.
- Remote formatter and editor-store tests for push, force-push, publish, sync
push-stage, and sync non-push-stage paths.
- Renderer derivation and `CommitArea` tests for Push blocked, Details, AI Fix,
hidden AI actions, stale branch/sequence, ordinary remote errors, and
existing commit failure recovery.
- Generic launcher tests for local/SSH, invalid args, saved-agent unavailable,
empty template, and success launch.
- Settings tests for global and repo Push failure fixes rows.
- Electron validation for all required screenshots; no separate E2E is
required if renderer tests cover the model and Electron validates the real UI.
- Performance/blast radius: No polling, watchers, migrations, or startup work.
Runtime cost is one bounded classifier pass over at most 64 KiB, bounded prompt
output, and a bounded file snapshot only when a remote action fails. Largest
blast radius is the shared recovery UI and launcher used by existing commit
failures, so commit recovery needs tests and a screenshot.
- UI quality bar: Match `docs/STYLEGUIDE.md` and adjacent Source Control density:
token-based error state, stable split-button geometry, progressive Details
disclosure, no nested cards, concise copy, light/dark compatibility, and
visible disabled/loading state under SSH latency.
- Required review screenshots:
1. Push blocked notice after pre-push hook failure.
2. Push failure Details dialog with Fix with AI.
3. `fixPushFailure` customize-launch dialog.
4. Ordinary push auth/protected/remote failure with no Push blocked panel.
5. Sync non-push-stage failure with no Push blocked panel.
6. Source Control AI hidden: Push blocked without AI Fix.
7. Source Control AI settings showing Push failure fixes.
8. Existing commit-failure recovery notice after shared UI extraction.
- Residual risks: Classifier false positives remain possible for unusual local
hook-runner output that mentions push context; keep tests biased toward
preserving auth, protected-branch, and non-fast-forward behavior. Failure
panels are renderer-local, so another Orca window or an external terminal can
fix the problem while the old panel remains visible until local retry, remount,
or branch/worktree change. Electron validation may need a mocked or temporary
repo scenario to trigger a real pre-push hook without mutating user data.