Conversation
When `updates.committed_local_changes: rebase` could not replay a local
patch stack, the operator was shown a single line: `Rebasing (1/6)`.
git splits rebase-conflict output across streams. stdout carries the
useful part (`CONFLICT (content): Merge conflict in <path>`); stderr
carries `Rebasing (n/m)`, `error: could not apply <sha>...` and `hint:`
lines. The failure path read `(stderr or stdout)` — stderr is non-empty,
so stdout was discarded wholesale — and then took `.splitlines()[0]`,
which is the progress banner. Every actionable detail was dropped twice.
Add `_rebase_conflict_summary`, which scans BOTH streams for the failing
commit and reads the unmerged paths out of the index while the conflicted
rebase is still live (`git rebase --abort` destroys that state moments
later), falling back to scraping stdout `CONFLICT` lines for rename/delete
conflicts that leave no unmerged entries. Also print a copy-pasteable
command to finish the rebase by hand.
Before: `Rebasing (1/6)`
After: `error: could not apply f231e8d... local patch`
`Conflicting file(s): conflict.txt`
`Resolve by hand with: git -C <root> rebase --empty=drop ...`
Co-Authored-By: Claude <noreply@anthropic.com>
|
Thanks for addressing a real destructive updater path. Current main still hard-resets after a failed fast-forward merge at Problems
Suggested changes
Automated hermes-sweeper review. |
SummaryThree PRs address unsafe handling of committed local patches across two checkout shapes: #4142 and #70953 protect commits on the update branch from the failed-fast-forward hard-reset path, while #22037 reconciles a separate local-only branch that would otherwise be left inactive. #70953 is the stronger successor to #4142, whereas #22037 covers a complementary cause. Related pull requests
Duplicates#4142 is substantially duplicated and superseded by #70953 for update-branch divergence; #22037 is complementary because it handles a separate local-only branch. Suggested consolidationAuthor action on #70953: rebase onto current main and port the preserved-commit helpers, failed-merge handling, configuration default, and retained integration tests to the live updater/config locations identified by the maintainer-bot keep_open verdict. Keep #22037 open with a salvage path for separate-branch reconciliation, but require fail-closed Complex graphflowchart LR
classDef open fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
classDef merged fill:#dcfce7,stroke:#15803d,color:#14532d
classDef closed fill:#e5e7eb,stroke:#6b7280,color:#1f2937
classDef unverified fill:#f3f4f6,stroke:#9ca3af,color:#374151
classDef best stroke-width:3px,stroke:#b45309
classDef target stroke-width:3px,stroke:#4338ca
subgraph Dup4142 ["PRs duplicating each other"]
P4142["PR #4142 (closed)"]
P70953["PR #70953 (open)"]
end
class P4142 closed
class P70953 open
class P70953 target
click P4142 "https://github.com/NousResearch/hermes-agent/pull/4142"
click P70953 "https://github.com/NousResearch/hermes-agent/pull/70953"
Graph: solid arrow = fixes / best fix, dashed arrow = partial or unverified (see edge label); boxed group = PRs duplicating each other; amber border = best fix; indigo border = target; gray node = closed (state tag in the node label). Cross-PR triage: Reviewed 3 pull requests and 0 issues in this complex. Each diff was read against this issue; Assessment working set: 61 kB of PR diffs, 6 kB of issue/PR text, 3 kB of discussion (3 comments), 0 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch. |
Problem
hermes updateassumes all local work is uncommitted (and therefore stashed). An install whosemainis deliberately ahead of origin — carrying committed local patches — hits the ff-only-pull failure path, which unconditionally runs a hard reset toorigin/<branch>and silently destroys that work.What this does
Commit 1 — preserve committed local patches. Adds
updates.committed_local_changes(refuse|rebase, defaultrefuse, so behavior is unchanged unless opted in):refuse— if localmainis ahead, stop with reconcile instructions instead of discarding commits.rebase— create a durable backup ref (refs/hermes/update-backups/<branch>/<ts>, pruned to the newest 5 by reflog time), rebase the patch stack onto the remote, and continue the normal pipeline. On any failure it aborts, verifies HEAD is restored to the pre-pull SHA, and keeps the backup ref.The historical reset is retained verbatim for the
no_local_commitscase, which is the only situation it was ever correct for.Commit 2 — report what actually conflicted. When the rebase fails, the operator was shown one line:
Rebasing (1/6).git splits rebase-conflict output across streams: stdout carries
CONFLICT (content): Merge conflict in <path>; stderr carriesRebasing (n/m),error: could not apply <sha>...andhint:lines. The failure path read(stderr or stdout)— stderr is non-empty, so stdout was discarded wholesale — and then took.splitlines()[0], which is the progress banner. Every actionable detail was dropped twice._rebase_conflict_summaryscans both streams for the failing commit and reads unmerged paths from the index beforegit rebase --abortdestroys that state, falling back to scraping stdoutCONFLICTlines for rename/delete conflicts that leave no unmerged entries.Before:
After:
Testing
103 tests pass on this branch against current
main(test_update_committed_changes.py,test_update_autostash.py,test_cmd_update.py), including new coverage for the conflict-reporting path and the rename/delete fallback.Context
Both commits have been running in production on a self-hosted install that carries local patches; the rebase path reconciled a 332-commit upstream jump successfully, and the conflict reporting was what made a real two-patch conflict diagnosable.
🤖 Generated with Claude Code