mirror of
https://github.com/stablyai/orca.git
synced 2026-10-18 08:03:22 +00:00
* 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>
369 lines
20 KiB
Markdown
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.
|