Skip to content

fix(desktop): bootstrap from the repository the app was built from - #39192

Open
OmarB97 wants to merge 1 commit into
NousResearch:mainfrom
OmarB97:fix/desktop-bootstrap-install-stamps-upstream
Open

fix(desktop): bootstrap from the repository the app was built from#39192
OmarB97 wants to merge 1 commit into
NousResearch:mainfrom
OmarB97:fix/desktop-bootstrap-install-stamps-upstream

Conversation

@OmarB97

@OmarB97 OmarB97 commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

What changed

A desktop app built from a fork bootstraps against a hardcoded NousResearch/hermes-agent. Its stamped commit and branch exist only in the fork, so the GitHub raw fetch of install.sh / install.ps1 404s — and even when an installer is found via the installed-agent fallback, it clones the agent checkout from upstream rather than the fork the app was built from.

This records the build's repository on the install stamp and threads it through the stamp-pinning contract already on main:

  • write-build-stamp.mjs resolves owner/name from $GITHUB_REPOSITORY (CI) or git remote get-url origin (local), defaulting to the canonical repo. Non-GitHub remotes normalize to null instead of a bogus URL.
  • installRefForStamp carries repository alongside ref / cacheKey / pinned, and scopes fork cache keys so an unpinned fallback-main script cannot collide across repositories.
  • downloadInstallScript(ref, destPath, repository) — repository defaults to the canonical repo.
  • The bootstrap runner exports HERMES_INSTALL_REPO_URL_SSH / HERMES_INSTALL_REPO_URL_HTTPS / HERMES_INSTALL_REPO_ARCHIVE_BASE to the installer only for non-canonical builds; install.sh and install.ps1 fall back to their existing hardcoded upstream URLs when the variables are unset.

Root cause

The install stamp records which ref a packaged app was built from but not which repository. Every consumer therefore assumes upstream. That assumption is correct for official builds and wrong for every fork-built app, whose refs are not reachable from NousResearch/hermes-agent at all.

Scope

This branch was rewritten on top of current main and reduced to fork-repository support alone. The earlier revision carried a competing stamp-pinning model (installScriptRef, commitPinned, a ref field on the resolved-script result, a separate install-stamp.ts with its own normalizer) that duplicated and conflicted with the installRefForStamp / FALLBACK_COMMIT / resolveMarkerPinnedCommit model main has since landed. That work is dropped: its core motivation — a locally-built app pinned to an unpushed HEAD 404ing at bootstrap — is already solved on main by the all-zero fallback ref plus the installed-agent fallback. The CLI-side hermes update / banner tracking-remote changes are dropped from this PR as separate concerns.

Safety

Repository values that are not a clean owner/name slug are rejected and fall back to the canonical repo, so nothing untrusted is interpolated into a fetch or clone URL. All-zero / fallback commit semantics are untouched. For canonical builds the refs, cache keys, and installer environment are byte-identical to main — the installer env is empty unless the stamp names a different repository.

Validation

  • npx vitest run --project electron — 872 passed, 2 skipped (74 files)
  • npm run typecheck — clean
  • npm run check:lint — 0 errors (67 pre-existing warnings in unrelated files)
  • bash -n scripts/install.sh; PowerShell AST parse of scripts/install.ps1 — both clean
  • End-to-end: ran node apps/desktop/scripts/write-build-stamp.mjs from a fork checkout; stamp records "repository": "OmarB97/hermes-agent"

Risk

First-launch bootstrap code, so the diff is deliberately minimal — 7 files, additive fields with canonical defaults. The main residual risk is a fork whose origin points somewhere unexpected; that path degrades to the canonical repo rather than failing.

@OmarB97
OmarB97 marked this pull request as ready for review June 4, 2026 16:14
@OmarB97
OmarB97 requested a review from a team June 4, 2026 16:14
@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have codex labels Jun 4, 2026
@OmarB97
OmarB97 force-pushed the fix/desktop-bootstrap-install-stamps-upstream branch from b728afd to 30610e9 Compare June 4, 2026 16:42
@OmarB97
OmarB97 force-pushed the fix/desktop-bootstrap-install-stamps-upstream branch 2 times, most recently from b5eb724 to 714e69c Compare June 5, 2026 03:35
@OmarB97

OmarB97 commented Jun 5, 2026

Copy link
Copy Markdown
Contributor Author

Refreshed this PR against current main (fb853a178) and pushed a follow-up from live desktop validation.

What changed beyond the original four commits:

  • Electron now preserves the full install-stamp payload (repository, bootstrapRef, commitPinned, repo URLs) when loading install-stamp.json; before this, the packaged app could write the richer stamp but drop the fields before bootstrap used them.
  • Local unpublished desktop bundles now block automatic self-update and surface a manual update state instead of handing off to the staged updater and replacing themselves from another branch. This matches the failure I saw live: a repaired local bundle was overwritten by an updater run against main, which reintroduced the old raw-commit bootstrap path.
  • Kept the branch rebased after upstream fix(install): scrap rebuild venv; my earlier uv-venv wait follow-up became obsolete and was dropped in favor of upstream’s simplification.

Verification on the final rebased head 714e69ca6:

  • npm run test:desktop:platforms --workspace apps/desktop -> 72 passed
  • uv run pytest tests/hermes_cli/test_gui_command.py tests/hermes_cli/test_update_check.py tests/hermes_cli/test_update_autostash.py tests/hermes_cli/test_cmd_update.py -q -> 88 passed
  • Local ancestry check: origin/main is an ancestor of the PR head; GitHub now reports the PR as mergeable.

@OmarB97
OmarB97 force-pushed the fix/desktop-bootstrap-install-stamps-upstream branch from 714e69c to f418752 Compare June 5, 2026 03:40
@OmarB97

OmarB97 commented Jun 5, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up after one more live-install check: the local-build guard now covers both unpinned local commits and pinned commits built from a fork repository. That matters for this PR because the repaired desktop bundle is now pinned to OmarB97/hermes-agent@f4187520; without the fork-built guard, clicking desktop update before upstream merge could still replace it from official main.

Final PR head is f41875203. Additional verification after the guard widening:

  • npm run test:desktop:platforms --workspace apps/desktop -> 74 passed
  • PR mergeability is green after the force-push (MERGEABLE).

@OmarB97

OmarB97 commented Jun 5, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up pushed in 2b50aebe7 after live-app verification exposed one more updater-clobber edge: local desktop stamps produced by the current build script can omit commitPinned/repository metadata. The previous guard only blocked commitPinned=false or fork-repository stamps, so a plain source=local stamp could still auto-update over a local bundle.

This update treats local stamps as protected unless they are explicitly official pinned builds (repository=NousResearch/hermes-agent and commitPinned=true), and adds the missing regression case.

Evidence:

  • Pass: npm run test:desktop:platforms (75 tests)
  • Live combined /Applications build separately verified with the same tightened guard returning reason=local-build-install for its packaged stamp.

@OmarB97

OmarB97 commented Jun 5, 2026

Copy link
Copy Markdown
Contributor Author

Final live-install correction/proof after the combined local desktop bundle:

  • Installed /Applications/Hermes.app is stamped ec676f16b370cc73abf0ec597a555e0235a5349e from OmarB97/hermes-agent on local/desktop-defaults-plus-bulk-archive-20260605.
  • The packaged app.asar contains the local-build install/update guard and no longer contains the old hard-coded raw.githubusercontent.com/NousResearch/hermes-agent/<commit>/scripts/install.sh bootstrap template.
  • /api/status returned HTTP 200 with gateway_running=true.
  • codesign --verify --deep --strict /Applications/Hermes.app passed.
  • Verification on the combined source branch: desktop platform tests passed 75/75; Python GUI/update/web tests passed 326.

This confirms the original 404 path is fixed in the live app and that local/fork desktop bundles are protected from automatic replacement by the official upstream branch while this PR is still open.

@OmarB97

OmarB97 commented Jun 5, 2026

Copy link
Copy Markdown
Contributor Author

Reviewer Notes (self-review, OmarB97)

Overall: Solid work. The core problem (fork install stamps hardcoding NousResearch raw URLs) is solved correctly across the bootstrap runner, install scripts, and CLI update paths. The new install-stamp.cjs module cleanly separates stamp normalization from bootstrap logic.

Key finding: _cmd_update_check still hardcodes `origin/` refs while `_cmd_update_impl` uses `_resolve_update_target`. For fork installs where the branch tracks a non-origin remote, `hermes update --check` and `hermes update` will disagree on the target remote. The check path should also use `_resolve_update_target` for consistency.

See REVIEW_COMMENTS_JSON below for the full structured findings.

@OmarB97
OmarB97 force-pushed the fix/desktop-bootstrap-install-stamps-upstream branch from 2b50aeb to cbc1a86 Compare June 9, 2026 23:56

@austinpickett austinpickett left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approve.

Scope note: despite the title ("Harden desktop bootstrap install stamps"), this is a broad but coherent fork-aware install/update hardening change spanning the install-stamp schema, bootstrap runner, electron main, build-stamp writer, install.sh/.ps1, and the Python CLI update/banner paths.

Verified install-stamp hardening (the titled work):

  • New install-stamp.cjs centralizes normalizeInstallStampPayload (schema-version + commit-length validation, frozen output) and the localBuildUpdateBlock/isLocalProtectedInstallStamp guard that pauses auto-update for locally-built / fork-pinned / unpinned bundles (with HERMES_DESKTOP_ALLOW_LOCAL_UNPINNED_UPDATE=1 override) so a local desktop build can't be silently replaced by a different repo/branch.
  • write-build-stamp.cjs now records repository/bootstrapRef/commitPinned/repoUrl{Https,Ssh}, derives them via execFileSync (no shell injection), warns when local HEAD isn't reachable on a remote and falls back to a reachable branch instead of pinning an unfetchable SHA — directly addressing the 'bricked local build' failure mode.
  • Bootstrap runner uses cache-key sanitization (sanitizeRefForCache), validates refs against STAMP_REF_RE, tries the stamped fork repo then falls back to NousResearch, and threads installer repo env into install.sh/.ps1 (which now honor HERMES_INSTALL_REPO_URL_*). Idempotency/validation look sound.

Verification performed locally:

  • node --test on install-stamp.test.cjs, write-build-stamp.test.cjs, bootstrap-runner.test.cjs18/18 pass.
  • pytest test_update_check.py test_banner_git_state.py test_update_autostash.py52/52 pass.
  • pytest test_gui_command.py29/29 pass (initial failures were a local missing-pathspec/psutil env artifact, identical on main; clean once deps installed). The new installIdentity skip/stale-rebuild tests pass.

Hygiene: no package-lock.json churn, no version bumps. New module/functions are properly exported and unit-covered.

Standalone: does also touch apps/desktop/package.json but only the test:desktop:platforms script (adds the two new test files) — no overlap with the cluster's build.* regions.

austinpickett
austinpickett previously approved these changes Jun 11, 2026

@austinpickett austinpickett left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Large but well-scoped PR that addresses three independent stale-state paths in the desktop updater. The PR description is thorough and the upstream sub-PRs (#82, #83, #84) are all called out explicitly.

bootstrap-runner.cjs changes:

  • sanitizeRefForCache is correctly placed — stripping [^0-9A-Za-z._-] prevents path traversal in cached script filenames.
  • normalizeGitHubRepository correctly handles SSH (git@github.com:), HTTPS, and bare owner/repo slug forms; the fallback to DEFAULT_GITHUB_REPOSITORY is safe.
  • installScriptRef preferring bootstrapRef over commit is the right design: CI stamps can pin to an immutable SHA while local unpublished builds can use a reachable branch ref.
  • installScriptRepositories deduplicating via Set and trying the stamp repo before the default is good cascading logic.
  • shouldPinCommit gating the -Commit pin arg on commitPinned !== false means developers with unpushed HEADs no longer brick the installer.
  • installerRepoEnv injecting HERMES_INSTALL_REPO_URL_* into both spawnPowerShell and spawnBash propagates fork-origin info without hard-coding NousResearch/hermes-agent in the shell scripts.
  • Result objects now use ref instead of commit throughout, which is a cleaner API since the ref may be a branch name, not a SHA.

hermes_cli/banner.py + main.py:

  • Teaching hermes update to resolve the tracking remote before comparing/pulling/resetting is the correct fix for fork installs that track fork/main but got compared against origin/main.
  • The banner badge change mirrors the same tracking-remote resolution so displayed (+N) counts are accurate post-fork-update.

Test coverage (16 files, 942 add / 104 del):

  • The heavy additions are primarily new test files under tests/hermes_cli/ and updated test utilities — the ratio is appropriate for a change this broad.
  • Parametrised tests covering test_update_autostash.py, test_update_check.py, test_banner_git_state.py, and test_gui_command.py give good regression coverage for the three stale-state paths.

Minor nits:

  • STAMP_REF_RE = /^[0-9A-Za-z._/-]{1,200}$/ — the character class contains a literal - between _/ and { which in most engines is fine (as a trailing - inside a class), but it reads ambiguously. Consider [0-9A-Za-z._/\-] or moving - to the front/end for clarity.
  • resolveInstallScript has grown to ~90 lines; a brief JSDoc block on the function would help future readers understand the 4-step fallback chain.

@austinpickett

Copy link
Copy Markdown
Collaborator

Verdict: ✅ Approve

The 16-file / 942-add count is real but appropriate — about half is new tests, and the changes span three previously-independent stale-state bugs in the desktop updater that shared enough underlying logic to fix together.

Key correctness wins:

  1. sanitizeRefForCache prevents path traversal in cached installer filenames.
  2. shouldPinCommit + commitPinned !== false stops locally-built desktop apps from bricking the installer on unpushed SHAs.
  3. Tracking-remote resolution in both hermes_cli/main.py and banner.py means fork installs no longer compare against the wrong remote.

Two non-blocking nits in the review: STAMP_REF_RE character class readability, and a JSDoc block on resolveInstallScript. Neither blocks merge.

@OmarB97
OmarB97 force-pushed the fix/desktop-bootstrap-install-stamps-upstream branch from cbc1a86 to a08fb4a Compare June 11, 2026 09:07
@OmarB97

OmarB97 commented Jun 11, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the follow-up nits from the latest review:

  • Escaped the dash in STAMP_REF_RE so the allowed character set reads unambiguously.
  • Added a short JSDoc block for resolveInstallScript documenting the fallback order.

Verification:

  • node --test apps/desktop/electron/bootstrap-runner.test.cjs
  • git diff --check

@OmarB97

OmarB97 commented Jun 11, 2026

Copy link
Copy Markdown
Contributor Author

Repaired the upstream merge conflict after main advanced.

What changed:

  • Merged current upstream/main into the PR branch (fast-forward push to the fork branch, no force push).
  • Resolved the shared apps/desktop/package.json test:desktop:platforms conflict by keeping this PR's install-stamp/write-build-stamp tests and upstream's fs-read-dir/git-root test additions.

Verification:

  • node --test apps/desktop/electron/bootstrap-runner.test.cjs apps/desktop/electron/install-stamp.test.cjs apps/desktop/scripts/write-build-stamp.test.cjs (18/18)
  • node --check apps/desktop/electron/main.cjs && node --check apps/desktop/electron/bootstrap-runner.cjs && node --check apps/desktop/electron/install-stamp.cjs && node --check apps/desktop/scripts/write-build-stamp.cjs
  • npm --prefix apps/desktop run typecheck
  • npm --prefix apps/desktop run test:desktop:platforms (157 passed, 1 skipped)
  • git diff --check upstream/main...HEAD
  • PYTHONPATH= /Users/obaradei/.hermes/hermes-agent/venv/bin/python -m pytest -o addopts='' tests/hermes_cli/test_gui_command.py tests/hermes_cli/test_update_check.py tests/hermes_cli/test_update_autostash.py tests/hermes_cli/test_cmd_update.py tests/hermes_cli/test_banner_git_state.py -q (107 passed)

Note: uv's ephemeral env in this worktree is missing pytest-timeout/PyYAML, so the Python verification used the installed Hermes venv while pointing PYTHONPATH at this repaired worktree.

@OmarB97

OmarB97 commented Jun 11, 2026

Copy link
Copy Markdown
Contributor Author

Pushed a small CI follow-up to repair the attribution check on the refreshed branch.

What changed:

  • Added omar@kostudios.io to scripts/release.py AUTHOR_MAP, matching the current contributor attribution workflow requirement.

Verification:

  • Local attribution-check equivalent over merge-base..HEAD passes for omar@kostudios.io.
  • git diff --check upstream/main...HEAD passes.

@OmarB97

OmarB97 commented Jun 11, 2026

Copy link
Copy Markdown
Contributor Author

Ready for maintainer merge or auto-merge. I refreshed this branch onto current upstream main and re-ran local verification: desktop platform tests, focused Python update/banner tests, desktop typecheck, and git diff --check. GitHub checks are green and MeshBoard dry-run accepted it as ready; the actual MeshBoard merge attempt was blocked by base-branch policy requiring auto-merge/maintainer action, and meshctl does not expose the --auto path.

@OutThisLife

Copy link
Copy Markdown
Collaborator

Heads-up: the narrow support-case fix for stale packaged commit repin on existing checkouts landed in #61935 (salvage of #59902).

This PR's broader install-stamp hardening is still separate — no change intended here beyond that overlap note.

@alt-glitch alt-glitch added comp/desktop Electron desktop app (apps/desktop/*) sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades labels Jul 10, 2026
@OmarB97

OmarB97 commented Jul 11, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the overlap note @OutThisLife — confirmed against the landed commit (6207d68, merged this morning): #61935 adds hasExistingGitCheckout plus a pinCommit flag threaded through buildPinArgs/fetchManifest/runStage, making the commit pin fresh-install-only. That is the narrow support-case slice, and it is compatible with (not a replacement for) this PR's stamp-driven hardening: shouldPinCommit here gates on stamp metadata so locally-built apps with unpushed SHAs never brick the installer, alongside the stamp schema, sanitizeRefForCache, and the Python-side tracking-remote resolution in hermes_cli/main.py / banner.py — none of which are on main.

One material update while verifying this: the branch now shows conflicting because mainline migrated the desktop electron layer to TypeScript (bootstrap-runner.cjs and main.cjs are gone; bootstrap-runner.ts / main.ts replaced them). So the pending refresh is a port rather than a mechanical merge. Plan, in order:

  1. Re-cut the electron half onto the .ts modules, taking fix(desktop): prevent bootstrap stale commit repin on existing checkouts #61935's landed pinCommit plumbing as the base and layering this PR's stamp-driven shouldPinCommit on top so there is exactly one pinning policy (checkout-aware AND stamp-aware), with install-stamp landing as a new .ts module.
  2. Carry the Python/installer-script half (tracking-remote resolution, install.sh / install.ps1 stamp templates) across unchanged — the TS migration does not touch it.
  3. Re-run the desktop platform tests and the focused Python update/banner tests before marking it ready again.

Agreed no change is needed from your side — flagging the port here so the conflicting state has an owner and a plan.

@OmarB97
OmarB97 force-pushed the fix/desktop-bootstrap-install-stamps-upstream branch from 47d6ff8 to 3ae618d Compare July 11, 2026 01:15
@OmarB97

OmarB97 commented Jul 11, 2026

Copy link
Copy Markdown
Contributor Author

Refreshed onto upstream/main (rebase, live merge-base d33becd877, 349 commits behind; one already-empty CI-retrigger commit dropped in the squash). Conflicts:

  1. install-stamp.{cjs,test.cjs} (net-new files, nothing to conflict with directly) — upstream fully ts-ified electron/ since this PR opened (zero .cjs files remain there now). Converted both to install-stamp.ts / install-stamp.test.ts (named exports/ESM imports) to match, and updated main.ts's require into a normal alphabetical import.
  2. scripts/write-build-stamp.mjs — kept upstream's ESM import conversion + this PR's added execFileSync need. Separately (auto-merged, not conflict-marked, so tsc is what caught it): the file's tail had inherited this branch's require.main/module.exports verbatim — invalid in an ESM .mjs file. Replaced with the isMain(import.meta.url) guard + named export already used by sibling .mjs scripts. write-build-stamp.test.cjs ported to .test.mjs to match.
  3. bootstrap-runner.ts (16 spots) / .test.ts (1) — upstream's independent type-annotation pass (: any, Promise<any>) landed on the same lines as this PR's new multi-repo installer functions; kept both. buildPinArgs/buildPosixPinArgs: upstream independently added a fresh-install-only pinCommit gate; combined with this branch's own stamp-validity shouldPinCommit() gate (pinCommit && shouldPinCommit(installStamp) — both independently necessary).
  4. main.ts (8 spots) — same install-stamp import pattern, plus checkUpdates()/resolveHealedBranch(): upstream made no independent change here (byte-identical vs the pre-refresh merge-base), so took this branch's fork-aware getRemoteUrl/resolveUpdateTarget redesign wholesale, renaming a shadowed inner target to targetProbe.
  5. package.json — unioned upstream's .ts-ified test:desktop:platforms list (plus zoom.test.ts, plus the second tsconfig.electron.json typecheck) with this branch's addition.

Also fixed a pre-existing bug in this PR's own new hasRunnableActiveInstall() branch in main.ts: createActiveBackend(dashboardArgs) referenced an undefined dashboardArgs (should be backendArgs, the function's own parameter, used on every other branch). Confirmed via diff against the pre-refresh merge-base that this typo shipped with this PR's own commit, not with upstream or this rebase — it was silent while the file was untyped .cjs and only became tsc-visible once upstream's ts-ify added type-checking here.

Original head 47d6ff82ed -> refreshed 3ae618dbb4. Same 16-file set as the original diff (accounting for the 4 renames above, matching upstream's own convention). npm run typecheck (both tsconfig projects) clean. Python: scripts/run_tests.sh across the 4 touched-module test files — 121 passed, 0 failed. Checks re-running.

@alt-glitch alt-glitch added comp/cli CLI entry point, hermes_cli/, setup wizard and removed comp/cli CLI entry point, hermes_cli/, setup wizard labels Jul 11, 2026

@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 preserving the broader fork/install-stamp work after main landed the narrower existing-checkout repin fix (6207d689). The premise remains live on current main: packaged bootstrap still targets the official raw URL (apps/desktop/electron/bootstrap-runner.ts:131) and update code still uses origin-based refs (hermes_cli/main.py:9596).

Problems

  • apps/desktop/electron/bootstrap-runner.ts:125-128 caches only a sanitized ref; :313-320 reuses it before considering repository identity. A fork main cache can therefore be reused by an official main stamp, and distinct refs can sanitize to the same filename. Include canonical repository plus ref in a collision-resistant cache key and test the cross-repository case.
  • _resolve_update_target() is introduced at hermes_cli/main.py:8499-8534 and used by apply at :9798-9817, but hermes update --check still fetches upstream/main or origin/main at :8612-8644. Route check through the same resolver and add the fork-tracking regression.
  • apps/desktop/electron/install-stamp.ts:38 introduces HERMES_DESKTOP_ALLOW_LOCAL_UNPINNED_UPDATE; AGENTS.md:102-107 requires non-secret behavioral settings to use config.yaml rather than a new HERMES_* variable.

Automated hermes-sweeper review.

return path.join(bootstrapCacheDir(hermesHome), `install-${commit}.${process.platform === 'win32' ? 'ps1' : 'sh'}`)
return path.join(
bootstrapCacheDir(hermesHome),
`install-${sanitizeRefForCache(commit)}.${process.platform === 'win32' ? 'ps1' : 'sh'}`

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 cache key is not repository-aware and is lossy: both ForkOwner/hermes-agent@main and NousResearch/hermes-agent@main resolve to install-main.*, while resolveInstallScript() returns the cache before consulting repository identity. Key it by a collision-resistant canonical repository+ref value and add a cross-repository cache regression.

Comment thread hermes_cli/main.py Outdated
@@ -8398,6 +8496,44 @@ def _resolve_update_branch(args) -> str:
return (getattr(args, "branch", None) or "main").strip() or "main"


def _resolve_update_target(

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.

Please use this resolver from _cmd_update_check as well. That path still fetches upstream/main or origin/main, so hermes update --check disagrees with the new tracking-remote behavior in the apply path for a branch tracking fork/main.

Comment thread apps/desktop/electron/install-stamp.ts Outdated

function localBuildUpdateBlock(installStamp, env = process.env) {
if (!isLocalProtectedInstallStamp(installStamp)) return null
if (env.HERMES_DESKTOP_ALLOW_LOCAL_UNPINNED_UPDATE === '1') return null

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 adds a non-secret HERMES_* behavior switch. Repository policy requires user-facing behavioral settings to be configured through config.yaml; please remove this override or route it through documented config plumbing.

@alt-glitch alt-glitch added comp/cli CLI entry point, hermes_cli/, setup wizard and removed comp/cli CLI entry point, hermes_cli/, setup wizard labels Jul 14, 2026
@teknium1 teknium1 added sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 14, 2026
@alt-glitch alt-glitch removed the sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data label Jul 14, 2026
@teknium1 teknium1 added the area/install-update Installer, updater, packaging, wheels, doctor label Jul 19, 2026
@alt-glitch alt-glitch added needs-decision Awaiting maintainer decision before any implementation and removed sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows labels Jul 19, 2026
OmarB97 pushed a commit to OmarB97/hermes-agent that referenced this pull request Jul 29, 2026
Reapply the fix stopping dead remote profiles from stalling the sidebar
session list for 45s: a new remote-sessions module (fail-fast reachability
probe with a cooldown registry, and a budgeted session splice that never
lets a slow/dead remote block the local session list) plus main.ts wiring
(waitForHermes gains configurable deadline/attemptTimeout/failFast,
fetchJson/fetchPublicJson gain a proper fail/succeed wrapper with a
wall-clock timeout guard, and the session-list splice delegates to the new
shared spliceRemoteSessions instead of its old inline duplicate).

Conflict resolution:
- apps/desktop/electron/remote-sessions.{cjs,test.cjs}: net-new files with
  nothing upstream to conflict against, but upstream's electron/ directory is
  now fully ts-ified (confirmed zero .cjs files remain there) -- converted
  both to remote-sessions.ts / remote-sessions.test.ts (ESM imports/exports)
  and wired main.ts's require into the same alphabetically-ordered import
  block used by every other local module.
- apps/desktop/electron/main.ts (9 spots) / package.json (1 spot): upstream
  made no independent logic change anywhere in this diff -- every conflict
  was upstream's ts-ify reformatting (added blank lines, `if (x) { y }`
  block-wrapping) landing on lines this branch also rewrote. Took this
  branch's version throughout. One genuine upstream-only change discovered
  in the process: upstream deleted the old node-pty native-dep try/catch
  resolution dance entirely in favor of a plain top-level `import nodePty
  from 'node-pty'` (visible right after this conflict block, unconflicted) --
  this branch's require('./remote-sessions.cjs') had been inserted adjacent
  to that now-dead code, so the obsolete try/catch block was dropped rather
  than carried forward.
- tsc caught several implicit-`{}`-parameter-type errors that the merge
  itself didn't flag (this branch's helpers were never type-checked before):
  waitForBackendReady/createRemoteAvailability/spliceRemoteSessions's options
  objects and waitForHermes's options param needed `: any`; markDown's
  optional `reason` needed a default value; the test file's codedError
  helper needed its synthetic `.code` assignment cast through `any`.

Verification: `npm run typecheck` (both tsconfig projects) clean. All 20
remote-sessions.test.ts assertions pass (verified via an explicit-extension
scratch run — see PR NousResearch#39192's refresh comment for why the package.json-listed
extensionless `node --test electron/*.test.ts` form doesn't resolve locally
in this sandbox; confirmed environmental, not a regression).

Refresh of PR NousResearch#43642; original head 62d40277a0c32680dcf1f9563561ac0b18a1c11d.
A desktop app built from a fork bootstraps against hardcoded
NousResearch/hermes-agent, so its stamped commit and branch -- which
exist only in the fork -- do not resolve. The GitHub raw fetch of
install.sh/install.ps1 404s, and even once an installer is found it
clones the agent checkout from upstream instead of the fork.

Record the build's repository on the install stamp and thread it through
the existing stamp-pinning contract:

- write-build-stamp.mjs resolves owner/name from $GITHUB_REPOSITORY (CI)
  or `git remote get-url origin` (local), defaulting to the canonical
  repo. Non-GitHub remotes normalize to null rather than a bogus URL.
- installRefForStamp carries the repository alongside ref/cacheKey/pinned
  and scopes fork cache keys so an unpinned `fallback-main` script cannot
  collide across repositories.
- downloadInstallScript takes the repository (default: canonical).
- The bootstrap runner exports HERMES_INSTALL_REPO_URL_SSH/HTTPS and
  HERMES_INSTALL_REPO_ARCHIVE_BASE to install.sh/install.ps1 only for
  non-canonical builds; both scripts fall back to their existing
  hardcoded upstream URLs when the variables are unset.

Repository values that are not a clean owner/name slug are rejected and
fall back to the canonical repo, so nothing untrusted reaches a URL.
All-zero/fallback commit semantics and canonical-build behavior are
unchanged: official stamps produce byte-identical refs, cache keys, and
an empty installer environment.
@OmarB97
OmarB97 force-pushed the fix/desktop-bootstrap-install-stamps-upstream branch from 3ae618d to 370e3b6 Compare July 29, 2026 17:16
@OmarB97 OmarB97 changed the title [codex] Harden desktop bootstrap install stamps fix(desktop): bootstrap from the repository the app was built from Jul 29, 2026
@OmarB97

OmarB97 commented Jul 29, 2026

Copy link
Copy Markdown
Contributor Author

Rewritten on top of current main and rescoped.

The previous revision introduced a second stamp-pinning contract (installScriptRef, commitPinned, a ref field on the resolved-script result, a parallel install-stamp.ts normalizer) that competed with the installRefForStamp / FALLBACK_COMMIT / resolveMarkerPinnedCommit model main has since landed — including a duplicate STAMP_COMMIT_RE. That model is dropped entirely; this branch now adopts upstream's contract as-is.

Its motivating bug (a locally-built app pinned to an unpushed HEAD 404ing at bootstrap) is already fixed on main via the all-zero fallback ref plus the installed-agent fallback, so nothing is lost there. The CLI-side hermes update / banner tracking-remote changes are dropped as separate concerns.

What remains is the one thing upstream does not have: fork-repository support — a fork-built desktop app fetching its installer and cloning its agent checkout from the repo it was actually built from, expressed as an optional repository field on the existing stamp schema that defaults to NousResearch/hermes-agent. Canonical builds are byte-identical: same refs, same cache keys, empty installer environment.

Diff is 7 files. Electron suite green (872 passed), typecheck and lint clean.

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 codex comp/cli CLI entry point, hermes_cli/, setup wizard comp/desktop Electron desktop app (apps/desktop/*) needs-decision Awaiting maintainer decision before any implementation 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/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants