Skip to content

fix(update): reconcile local branches during update - #22037

Open
Niko96-dotcom wants to merge 1 commit into
NousResearch:mainfrom
Niko96-dotcom:fix/smart-update-local-branches
Open

fix(update): reconcile local branches during update#22037
Niko96-dotcom wants to merge 1 commit into
NousResearch:mainfrom
Niko96-dotcom:fix/smart-update-local-branches

Conversation

@Niko96-dotcom

Copy link
Copy Markdown

Summary

Teach hermes update to reconcile local fix branches against the updated upstream main.

When a user starts on a local branch, Hermes now uses git cherry origin/main <branch> after fetch to compare commits by patch-id:

  • commits already present upstream are treated as solved, so the update stays on main
  • commits still local-only are carried forward by rebasing the branch onto updated main
  • before rebasing, Hermes creates a hermes-update-snapshot/... safety branch
  • if rebase conflicts, Hermes aborts the rebase, leaves main updated, and prints recovery guidance instead of leaving the install conflicted

This makes the common "I have a local hotfix; did upstream fix this yet?" flow less scary without using filename or keyword guesses.

Docs

Updated the update guide and CLI reference to document local branch reconciliation.

Tests run

  • /Users/niko/.hermes/hermes-agent-provider-routing/.venv/bin/pytest -q tests/hermes_cli/test_update_autostash.py - 27 passed
  • /Users/niko/.hermes/hermes-agent-provider-routing/.venv/bin/python -m compileall hermes_cli/main.py
  • git diff --check

@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/cli CLI entry point, hermes_cli/, setup wizard labels May 11, 2026
@pinguarmy

Copy link
Copy Markdown

Real-world confirmation from a local dogfood/update flow: this is still a sharp edge for local-only patch branches.

Observed flow on macOS managed install (~/.hermes/hermes-agent):

⚕ Updating Hermes Agent...
→ Fetching updates...
  ⚠ Currently on branch 'local/patch-restore-20260607_143002' — switching to main for update...
→ Found 4 new commit(s)
  ✓ Pre-update snapshot: 20260607-151032-pre-update
→ Pulling updates...
...
✓ Update complete!

After the update, the active checkout was back on main (a317e5493), and all committed local patches from the local/patch-restore-* branch were inactive. Because the working tree was clean, the stash/restore prompt did not appear. A separate local checker then reported every expected patch group missing.

This was not uncommitted working-tree loss — the commits still existed on the local branch — but from the runtime/user perspective the update silently deactivated the local patched runtime.

Important nuance for this PR: the affected branch was local-only and had no meaningful remote tracking branch. A fix that only switches back/rebases against origin/<current-branch> may not cover this case. The useful behavior would be one of:

  • if update starts on a non-main local branch, update main then rebase the original branch onto updated main and switch back;
  • if rebase conflicts, leave clear recovery guidance and preferably a safety branch/snapshot;
  • at minimum, print a post-update warning that the runtime is now on main and the previous local branch's committed patches are no longer active.

This aligns with the user-facing problem described here: local contributors/dogfooders can think their patches “disappeared” after a successful update even though Git merely switched the active checkout away from their branch.

Related recent PR: #40673 also touches feature-branch update behavior/banner distance, but this local-only branch case seems closest to this PR’s scope.

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for addressing a real update safety gap. Current main still switches from a local branch to the update target at hermes_cli/main.py:9608-9622, while the successful-update path does not check the original branch back out; its only restoration is in the no-update return at hermes_cli/main.py:9668-9691.

Problems

  • hermes_cli/main.py:6187-6191 treats a failed git cherry invocation as an empty comparison. Then hermes_cli/main.py:6252-6253 treats it as normal and permits the update to remain on main, recreating the silent deactivation scenario on comparison failure.
  • The submitted control flow predates current --branch support: it hard-codes main at hermes_cli/main.py:7310-7312, whereas current main resolves the update target at hermes_cli/main.py:9562-9568.
  • The new tests cover a successful rebase (tests/hermes_cli/test_update_autostash.py:555-581) but not the promised rebase-conflict recovery or failed git cherry path.

Suggested changes

  • Model comparison failure explicitly and fail closed with recovery guidance.
  • Integrate the reconciliation around current target-branch semantics and add the two failure-path tests.

Automated hermes-sweeper review.

Comment thread hermes_cli/main.py
return _LocalPatchStatus(already_upstream=[], local_only=[])

if result.returncode != 0:
return _LocalPatchStatus(already_upstream=[], local_only=[])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

A failed git cherry comparison is not equivalent to an empty comparison. This return flows through has_commits == False and leaves the update on main without a warning, recreating the silent deactivation case on any comparison error. Return an explicit unknown/error status and fail closed with recovery guidance.

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform area/install-update Installer, updater, packaging, wheels, doctor labels Jul 13, 2026

@GottZ GottZ left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This was generated by AI during triage.

Summary

Three PRs address the update path’s unsafe handling of committed local patches, but they cover two distinct checkout shapes: #4142 and #70953 protect commits carried directly on the update branch, while #22037 reconciles a separate local-only branch that the updater otherwise deactivates by switching to the update target and remaining there.

Related pull requests

  • #4142 [closed] related — (+57/-16) — superseded: This closed PR detects commits ahead on the update branch and rebases them instead of hard-resetting, but it lacks the durable backup, fail-closed inspection, configuration, and integration coverage in #70953; it remains relevant as the earlier implementation of the same core fix and was closed pending a fresh rebase.
  • #22037 related — (+283/-1) — keep open and revise: This is the only PR that directly addresses silent deactivation of a separate local-only patch branch by comparing patch IDs, restoring and rebasing still-local patches, and leaving upstream-equivalent patches behind. The contributor keep_open review must be resolved before merge: the current diff converts failed git cherry execution into an empty successful comparison, hard-codes main instead of honoring current --branch target resolution, and does not test comparison failure or rebase-conflict recovery.
  • #70953 related — (+760/-29) — complementary preferred implementation for update-branch divergence: This PR safely handles committed patches directly on the selected update branch through fail-closed inspection, default refusal or opt-in rebase, durable backup refs, verified abort recovery, and real-Git conflict tests. It substantially supersedes #4142, but it does not address #22037’s distinct case where patches live on a separate local-only branch that is switched away from before the pull.

Duplicates

#4142 and #70953 substantially overlap on preserving committed patches carried directly on the update branch; #70953 is the broader and safer successor. #22037 is not a full duplicate because it covers reconciliation of a separate local-only branch.

Suggested consolidation

Merge #22037 only after addressing the explicit contributor keep_open review by making git cherry failure fail closed, using the resolved --branch update target, and adding comparison-failure and rebase-conflict recovery tests; preferably reuse #70953’s durable-backup and verified-recovery mechanics. Close #4142 as superseded by #70953; do not close #70953 as a duplicate of #22037 because its update-branch safety scope remains distinct.

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.

@GottZ GottZ left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This was generated by AI during triage.

Delta since our previous triage comment

@teknium1’s new review confirms that #70953 addresses a still-live destructive hard-reset path, while correcting our earlier assessment of its merge readiness: the updater has moved from hermes_cli/main.py to hermes_cli/update_cmd.py, and the config default has moved to hermes_cli/config_defaults.py. The review also confirms that #70953 remains complementary to #22037 rather than replacing its separate local-branch reconciliation scope.

Changed pull requests

  • #70953 related — (+760/-29) — keep open and port before merge: The diff still provides the stronger update-branch reconciliation design—fail-closed refusal or backed-up rebase with verified conflict recovery—but, as @teknium1’s keep_open review identifies, it currently modifies obsolete implementation and config locations; it must be ported to hermes_cli/update_cmd.py using the _m() seam and to hermes_cli/config_defaults.py, then rerun its integration tests.

Suggested consolidation

The consolidation recommendation is unchanged: revise #22037 for its reviewed local-branch gaps, retain #70953 for the distinct update-branch fix after porting it to the live updater, and keep #4142 superseded by #70953.

Complex graph unchanged since our previous triage comment.

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, 5 kB of discussion (4 comments), 0 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/install-update Installer, updater, packaging, wheels, doctor comp/cli CLI entry point, hermes_cli/, setup wizard P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants