Commit Graph

225 Commits

Author SHA1 Message Date
Teknium
dbbf98371d perf(install): a pinned install.ps1 clone checks out only the pin, before it publishes
Mirrors the install.sh change from #135092 on Windows. With -Commit (what
the desktop bootstrap passes), the fresh clone runs with --no-checkout and
the pin is checked out with retries inside the staging dir, so the branch
tip is never materialized first and a refused or failed pin never publishes
a checkout. A rerun over an existing checkout keeps pinning after the fetch.
2026-10-08 15:15:33 -07:00
Teknium
5f7f9e143d fix(install-metrics): a Ctrl-C a stage handles no longer aborts the install; user aborts read interrupted
The ladder's INT/TERM traps wrote the receipt and exited as soon as the
foreground stage returned, even when the stage's child caught Ctrl-C and
succeeded (setup's provider picker prints "Provider setup skipped." and
carries on). gateway and complete were skipped, the bootstrap marker was
never written and rc went 0 -> 130: a telemetry write changed installer
control flow.

install.sh:
- The ladder traps (INT, TERM and now HUP, a closed terminal) only note the
  signal while a stage runs. rc==0 clears a Ctrl-C and the ladder carries on,
  as bash's cooperative exit did on main; rc!=0 with a signal noted writes
  `interrupted` and re-raises the signal. Outside a stage (platform check,
  banner, between stages) the signal stops the run at once with a receipt.
  The traps are installed with the EXIT trap, before check_platform.
- Inside a ladder stage, a signal marks STAGE_INTERRUPTED; stage_result
  writes `interrupted` over the stage's own class, so pm.cli /
  source_completion / `hermes setup` catching KeyboardInterrupt and exiting 1
  no longer reads deps_install_failed / setup_failed. A child the signal
  killed still ends the stage at once (status 128+N).
- A TERM/HUP sent to the installer alone takes effect when the running stage
  returns (documented; the stage stays in the foreground because a
  background stage's children ignore Ctrl-C under bash 3.2), and the receipt
  names the stage the run stopped before, not a finished one.
- A stage killed outright (SIGKILL/OOM) now leaves a `failed/<stage>/other`
  receipt; a failed write (full disk) removes its .tmp; the fallback id uses
  $(( RANDOM*32768+RANDOM )) (no octal parse); no clock -> no receipt.

install.ps1:
- A native child that ended on Ctrl-C (130 or STATUS_CONTROL_C_EXIT) reads
  `interrupted` over the stage's class; the receipt write removes its .tmp
  on failure; the dead `other` class on the pre-ladder Initialize-ResolvedPaths
  Fail is dropped (it runs before the ladder's try, so it never wrote one).

Tests (real install.sh main block with stub stage bodies, real SIGINT to the
process group): a handled Ctrl-C finishes the install with a success
receipt; a child that turns Ctrl-C into exit 1 reads interrupted. Both red
at 6246dd0f69d.
2026-10-06 11:10:43 -07:00
Teknium
faf3a1e5c0 fix(install-metrics): one receipt per install run, interrupted runs counted, latch after save
install.sh: a run id per ladder, first writer wins (a parent-only SIGTERM no longer
leaves two receipts); Ctrl-C / SIGTERM write failure_class=interrupted.
install.ps1: same once-only guard, finally{} records an interrupted run.
Reporter: latch a receipt id only after its row is saved, so a short-lived start
whose daemon reporter is cut off at exit re-reports on the next start.
2026-10-06 11:10:43 -07:00
Teknium
74e0225377 feat(observability): hermes.install.run from a local installer receipt
install.sh / install.ps1 leave one closed-token receipt per full ladder run
(installer, outcome, failed_stage, failure_class, start/finish epoch) under
$HERMES_HOME/telemetry/shared_metrics/pending_installs. Every fail()/Fail call
site passes a closed failure class; the installer never sends anything.

The next Hermes start records each receipt once (latched by id, deleted after
the row is saved) only while collection is on; a start with collection off
purges the receipts unreported. Out-of-contract receipts are dropped.
2026-10-06 11:10:43 -07:00
Teknium
1c97b57a93 fix(update): convert a treeless checkout before the dependency work, and on installer re-runs
A Desktop built before the blobless fix keeps walking history during
`hermes update`. On a treeless (tree:0) checkout every walk downloads trees
again, and #129514 filled a 434 GB disk during the minutes of dependency work.
The one-time conversion (#133016) ran at the very end of the update, after that
window. Both update hand-offs (update_completion._prepare and an older
updater's _update_takeover.prepare) now run it first, in the bootstrap
interpreter, and the late call is gone.

Re-running install.sh / install.ps1 over a treeless checkout now converts it
too. The filter is recorded only after the refetch succeeds, so a failure is
retried by the next update instead of leaving a blob:none filter over a
history that has no trees.

The pack cleanup also sweeps tmp_* transfer files that killed fetches left
behind. They are more than an hour old, a storm leaves gigabytes of them,
and git itself only prunes them after two weeks. The docs gain the recovery
steps for a disk that the storm already filled.

Co-authored-by: Brooklyn Nicholson <brooklyn.bb.nicholson@gmail.com>
Co-authored-by: Yuan Li <dskwelmcy@163.com>
2026-10-05 17:32:54 -07:00
Teknium
1d9bb51f6b fix(install): clone blobless, convert treeless checkouts once
Installers have cloned with --filter=tree:0 since c3e9ac87a7. A treeless
clone keeps commits only, and git requests a missing tree without telling
the server which trees it already has, so every checkout and every
path-filtered history walk re-downloads complete directory snapshots
(~0.58 MB per commit for this repo). GitHub's own guidance is that
treeless clones are for throwaway CI builds, not long-lived checkouts;
the blobless layout keeps every commit and tree local and fetches only
file contents on demand.

- install.sh / install.ps1 clone --filter=blob:none (same release tags,
  --commit pins and ancestor checks the treeless switch was made for).
- hermes update (post-update step) converts an existing tree:0 checkout once with
  `git fetch --refetch --filter=blob:none origin` (~120 MB, ~8 s
  from GitHub) and records the new filter only after it succeeds, so a
  failed refetch retries next update.
- The shallow-heal conversion unshallows as blob:none instead of tree:0.
- checkout_contains asks git with GIT_NO_LAZY_FETCH: a recorded SHA the
  checkout lacks is "not contained", never a download of that commit's
  whole history.
2026-10-05 01:30:17 -07:00
Brooklyn Nicholson
667b232535 fix(install): let HERMES_RUNTIME_DIR decide where the guarded store lands
The HermesHome/InstallDir guard checked HermesHome first and never looked at
HERMES_RUNTIME_DIR once HermesHome sat inside InstallDir, so the documented
escape hatch could not lift the refusal (Windows lane: store-guard case 4).
The store is HERMES_RUNTIME_DIR when set, else under HermesHome; judge that.

The runtime.ps1 Unicode tests read the helper's answer through a UTF-8 file:
under code page 437/936 the console cannot carry the profile's characters,
so a stdout capture lost them (and the 65001 cell's piped ConvertTo-Json
unrolled the one-element array into a string).
2026-10-04 13:23:25 -05:00
Brooklyn Nicholson
732b63d61a fix(install): decode launcher path captures as UTF-8 and refuse a HermesHome inside InstallDir
Extends #124526's class fix to the two remaining native captures that can
carry a profile path, and closes the secondary issue from the report:

- scripts/desktop-update/runtime.ps1: the launcher --print-runtime-command
  JSON and the legacy user-bin --version 'Install directory:' capture are
  both scoped to [Console]::OutputEncoding = UTF-8 and restored in finally,
  matching install.ps1's Invoke-Native -Utf8Output and every other consumer
  of the boundary (windows_ssh_runtime, the Electron updater, the Rust
  bootstrap all decode UTF-8). PS 5.1's OEM decode mojibaked the reported
  install root so the legacy identity check never matched on a non-ASCII
  profile.
- scripts/install.ps1: -HermesHome equal to or inside -InstallDir puts the
  pm tool store inside the checkout; the repository stage's occupied-
  directory preflight then refuses every retry after the first run
  populated <InstallDir>\tools. Refused up front unless HERMES_RUNTIME_DIR
  parks the store elsewhere.

Tests: tests/scripts/desktop_update/test_desktop_update_runtime_unicode.py
(real published launcher, non-ASCII profile, CP936/437/65001, encoding
restore) and scripts/tests/test-install-ps1-store-guard.ps1 (real installer
subprocess through -ShowResolvedPaths: refusal, escape hatch, defaults).

Co-authored-by: 择梦舟 <158067205+dreamfarer-space@users.noreply.github.com>
2026-10-04 13:23:25 -05:00
择梦舟
eb35beb955 fix(install): preserve Unicode bootstrap Python paths on Windows 2026-10-04 13:23:25 -05:00
teknium1
35eacd248a fix(update): keep git's own lazy-fetch pack fold on in a partial clone
#131679 set maintenance.auto=false in the treeless install checkout to stop the
commit-graph lazy-fetch loop (#127711). That also switched off the post-fetch
`gc --auto` that git <= 2.53 runs after every lazy fetch, so packs piled up
between updates and only folded on a manual `hermes update` / `--check`
(nothing runs `--check` automatically).

The loop needs a commit-graph write, and gc.writeCommitGraph=false +
fetch.writeCommitGraph=false already stop gc and fetch from writing one.
maintenance.commit-graph.enabled=false closes the last writer (a commit-graph
maintenance task, e.g. from a user's global maintenance.strategy) while
leaving automatic maintenance, and its gc fold, on.

Checkouts that already got maintenance.auto=false from the first cut are
converged: the next update/--check unsets exactly that value.

Measured on a real tree:0 clone with a Bloom commit-graph, 100 lazy fetches
after one updater run:
  git 2.53.0: main 101 packs, this change 1 pack, 0 commit-graph writes
  git 2.55.0: 101 / 101 (stock 2.55 maintenance does not fold), 0 writes
2026-10-02 14:17:41 -07:00
ethernet
6880ffc1af fix(update): make the partial-clone pack fold and maintenance keys work together (#129712, #127711)
The two salvaged commits above disagree: #127721 persists gc.auto=0, which
turns the fold's `gc --auto` into a no-op (106 packs stay 106 on git 2.50.1,
2.53.0 and 2.55.0), and the fold itself is exposed to the loop #127721
guards against. Probed on all three gits against a blobless clone whose
commit-graph carries changed-path (Bloom) data:

- gc, `gc --auto`, `maintenance run --task=commit-graph` and
  fetch.writeCommitGraph each lazy-fetch the trees of every commit the graph
  has not seen (30 unseen commits: 30 fetches, 30 packs), and every lazy
  fetch spawns `git maintenance` again. gc.writeCommitGraph=false stops it
  (gc: 31 packs -> 1).
- Without a Bloom graph none of that happens, so the keys are a guard, not a
  fix for every partial clone.

Changes:
- keys: maintenance.auto, gc.writeCommitGraph, fetch.writeCommitGraph; gc.auto
  stays default. Idempotent (no rewrite when already set), `--local`, hidden
  console on Windows. install.sh and install.ps1 set the same three keys.
- consolidate_lazy_fetch_packs applies the keys before the fold, so installs
  that predate the installer change converge on their next update check.
- the fold runs under bounded_probe_run: subprocess.run(timeout=) kills only
  `git gc` and leaves `pack-objects` running (reproduced), and with
  GIT_NO_LAZY_FETCH.
- dropped the post-gc mark_unmarked_packs_promisor: gc keeps the .promisor
  marker on 2.50.1, 2.53.0 and 2.55.0.
- docs: updating.md troubleshooting entry.
2026-10-02 11:41:19 -07:00
kshitijk4poor
596678d665 refactor(update): tighten the partial-clone heal after review
- _deepen_shallow_repo marks packs in its finally without a git config probe
  that could itself raise and turn a finished unshallow into False; markers
  are inert in a clone without a promisor remote.
- Installers warn instead of aborting the stage when a marker cannot be
  written, matching the Python helper.
- Docs: the manual commands follow HERMES_HOME, and the missing-object
  backfill feeds IDs through `fetch --stdin` so a large list cannot exceed
  the Windows command-line limit.
- Test and docstring comments describe the contract, not the incident.
2026-09-29 15:24:15 +05:30
kshitijk4poor
7b20b72269 fix(install): installer rerun heals a partial clone stuck on the pack-objects crash
An install whose updater dies on the git 2.53+ pack-objects BUG (#124272)
never fetches the release that heals it: the fetch runs in the installed
code. The installer rerun (install.sh / install.ps1, served fresh) is the
path that runs new code, so when origin is a promisor remote it now marks
the checkout's unmarked packs before its existing-checkout fetch. Marking is
idempotent and never rewrites objects, so it runs up front rather than as a
retry that would first print a failed-fetch report.
2026-09-29 15:24:15 +05:30
teknium1
1d287d5375 fix(browser): ship the Browser Use CLI engine in every install, Desktop included
The default browser_exec tool ran the `browser-use` CLI from a PM side
environment (browser-use==0.13.10 in <home>/environments/browser-use),
provisioned by the installers and `hermes update`. Sealed Desktop payloads
skip that step, so the Desktop app never had it and silently fell back to
the built-in tools; the side env was also per-profile and 225 MB.

The CLI's execution path is only `browser_harness.run.main()`; the
browser-use agent framework (anthropic/openai/google-api pins, 93 MB of
googleapiclient) is never imported. browser-harness itself is 2.6 MB of
pure Python whose pins (Pillow 12.3.0, websockets 15.0.1) already match
Hermes's own, so it becomes a core dependency and runs on sys.executable:

- pyproject/uv.lock: browser-harness==0.1.13 (+ cdp-use, fetch-use).
- _find_cli() returns [sys.executable, -m, browser_harness.run]; the child
  env points PYTHONPATH at the harness site dir (the Desktop store
  interpreter boots without a venv and the harness daemon re-runs
  sys.executable), replacing whatever the agent inherited.
- The side-env provisioning (install_cli, the update/installer step) goes.
2026-09-27 23:53:40 -07:00
teknium1
b63c138d78 fix: Hermes never falls back to the user's node/npm/npx/uv
Maintainer ruling: only Hermes and only its packaged package managers
(uv/pip/node/npm) are ever used; no PATH fallback when the managed tool is
missing, no "prefer the user's if new enough".

- hermes_constants.find_node_executable: node/npm/npx resolve to PM's
  installed copy or None. Every caller already pm.ensure()s on None, so a
  missing runtime is now provisioned instead of silently borrowing the
  user's Node (native-addon ABI / npm cache mismatches).
- agent/lsp/install._install_npm: pm.ensure('npm') when PM npm is absent,
  instead of failing over to whatever npm is on PATH.
- gateway._append_node_dir_for_service: stop baking the invoker's PATH node
  dir into generated systemd/launchd units.
- main_install_repair._resolve_node_runtime_npm: drop the PATH re-scan for
  another npm.
- source_build.source_product_current: run the freshness reader only with
  PM's node.
- doctor: Node/npm rows and npm audit use PM's copies (Termux APT distro
  keeps its system Node).
- install.sh ensure_uv / install.ps1 Get-Uv: always stage the pinned uv
  artifact; delete the "uv on PATH if new enough" developer shortcut.
2026-09-27 22:04:26 -07:00
teknium1
27062c3474 fix: install cua-driver and the Browser Use CLI by default again
Computer use and browser use are meant to work out of the box. The PM rewrite
(3d12e86ef1) and the MSIX installer rework (47f4ab3a17) dropped the
install-time cua-driver fetch (7060ac7bed) and the Browser Use CLI install
(baa6b2e34d). On a fresh install the computer_use check_fn therefore stayed
False, so the tool never reached the model and its lazy ensure could not fire,
and browser_exec quietly fell back to the built-in tools.

- cua-driver is a default PM package, so the installers, a bare
  `hermes pm install` and `hermes update` carry it on every target it builds
  for. Adds the missing Android gap (the lock has no bionic artifact).
- The shared default-tool step, which the installers (via source completion)
  and `hermes update` both run, provisions the Browser Use CLI for the default
  and explicit Browser Use backends. `--skip-browser` declines it along with
  agent-browser, and `off`/Camofox never use it.
- Installers regain --skip-computer-use / -SkipComputerUse (recorded as
  `--without cua-driver`).
- The update message stops calling every default "browser tools".
2026-09-27 19:18:58 -07:00
kshitijk4poor
36c91657da fix(install): kill the PortableGit extractor tree on timeout, drop dead flatten
Process.Kill() on PS 5.1 kills only the stub; its post-install children kept
%TEMP%\hermes-git-bootstrap-<PID> held past the finally cleanup. taskkill /T /F
reaps the tree. The single-wrapper-dir flatten is gone: the sha-pinned
PortableGit roots cmd\git.exe directly (verified against the real archive).
2026-09-28 02:04:08 +05:30
finn763
7a40c28750 fix(pm): execute the pinned git SFX from a scratch copy, not the cache
CI run 36189416163 failed after "git: unpacking": executing the cached
fetch-<sha> PortableGit PE in place left it handle-held (Defender
on-execute scan / the stub's RunProgram child chain) past pm's ~2 s
_remove_entry retry, so download cleanup raised WinError 32. Review
(teknium1, 5 threads) adds the rest:

- Git.unpack now copies the artifact into a .sfx-* dir beside the
  staging tree and executes the copy; pm's .staging-* teardown
  (ignore_errors) owns that path, so any hold lands on a disposable
  path and the cache dir only ever holds read handles
- refuse off-Windows with the cross-host trade-off stated instead of a
  raw PermissionError; documented in package-management.md and the PR
- the extractor is a GUI-subsystem stub that is silent under -y: error
  messages now carry the exit code and the usual causes (disk full,
  path length, antivirus) instead of promising captured output; the
  docstring no longer claims "no GUI" and records that the stub shows
  an Extracting window and runs the vendor post-install
- install.ps1: WaitForExit(600000) + Kill() mirrors pm's timeout=600,
  Fail reports the exit code and the silence
- tests: the OS-refused-exec assertion and the not-in-text change
  detectors are replaced by subprocess-argv invariants (scratch copy
  location, exit-code message, off-Windows guard before any execution)

Related to #122512

(cherry picked from commit adaf76a286bebe30c89aee4174df27f1945b60c9)
2026-09-28 02:04:08 +05:30
finn763
6d9ae2da6f fix(install): stage pinned git without tar/bzip2 (PortableGit SFX)
Windows 10 boxes whose System32 tar.exe cannot run the bzip2 filter die
at stage=prerequisites with "unable to run program bzip2 -d" while
extracting the pinned Git-2.53.0.3 tar.bz2 (#122512). Repin git for
both win32 targets to git-for-windows' PortableGit self-extracting 7z,
which carries its own extractor and the bundled usr/bin/bash.exe:

- pm/lock.json: new artifact urls + sha256 (the pin authority)
- scripts/install.ps1: generated fragment regenerated; Get-PinnedGit
  downloads the SFX and waits on it explicitly (the stub is a
  GUI-subsystem exe, so PowerShell's & does not wait); the System32
  tar invocation and its msys symlink excludes go away
- pm/packages.py: Git.fetch_url/Git.unpack run the self-extractor
  after the sha256-verified download
- pm/store.py: drop the now-callerless git_msys branch of extract_tar
- tests: RED->GREEN test runs the real Get-PinnedGit against a
  bzip2-less System32 tar.exe stub with no bzip2 on PATH; the three
  obsolete tar-contract tests and the install.ps1/PM msys-links parity
  test are replaced by a no-external-decompressor contract test

Closes #122512

(cherry picked from commit 4915304213495d3207ec6cd659e57cd16ef08d60)
2026-09-28 02:04:08 +05:30
kshitijk4poor
44e42b2502 fix(install): re-runs update tag-pinned narrow checkouts instead of aborting
The installer's re-run path fetched `origin <branch>` by name, then checked
the branch out and fast-forwarded to origin/<branch>. On a checkout an older
installer made with `--depth 1 --single-branch --branch <tag>`, the remote's
only refspec maps the tag: the fetch wrote FETCH_HEAD but no origin/<branch>,
and `git checkout main` failed with "pathspec 'main' did not match".

Fetch by explicit refspec (as `hermes update` now does), and when there is no
local branch yet create it at the fetched tip — checkout's own branch guess
ignores remote refs the configured refspec does not map. The remote's fetch
config is left as the user has it. Same change in install.sh and install.ps1.
2026-09-28 01:53:35 +05:30
Hermes Agent
c5380053b5 fix(install): keep the ffmpeg lock label, drop the network liveness test, document -SkipSetup
Review fix-ups on top of the re-pin:

- pm/lock.json: keep "version": "9.0.1". pm keys the store entry on
  `ffmpeg-<version>-<target>` and reinstalls on the artifact sha alone
  (pm/install.py::_identity / _entry_current), so the sha change already
  re-fetches the four BtbN targets. Bumping the label would also rename the
  macOS (martin-riedl, still 9.0.1) and Termux entries and re-stage unchanged
  bytes on every existing install for no reason.
- tests/pm/test_ffmpeg_pin_liveness.py: removed. It HEADs live GitHub URLs
  from the unit lane, and BtbN prunes dated autobuild tags after ~14 days
  (autobuild-2026-09-10-15-31 is already gone), so the test turns red for
  every PR on ~Oct 11 by construction. Upstream rot is what
  archive-inputs.yml (sha256 mirror on merge) and #122433 (re-pin on fetch
  failure) are for.
- website/docs/user-guide/windows-native.md + install.ps1 header: say that
  -SkipSetup is accepted as a deprecated alias for -NonInteractive instead of
  claiming it is rejected.

(cherry picked from commit def90331c2; ffmpeg lock/liveness hunks dropped, superseded by #125468)
2026-09-28 00:58:16 +05:30
Yuan Li
54421a43c6 fix(install): accept -SkipSetup alias and re-pin ffmpeg to a live BtbN tag (#125350)
The staged-installer rework (92686159d1) dropped the -SkipSetup switch
from install.ps1, so wrappers written against the old spelling
(hermes-desktop -SkipSetup -NonInteractive ...) die at parameter
binding with NamedParameterNotFound. Accept it as a deprecated alias
that folds into -NonInteractive.

pm/lock.json pinned ffmpeg artifacts to the dated BtbN autobuild tag
2026-09-10-15-31; BtbN prunes old dated tags, so fresh installs 404 on
GitHub and the sha256 mirror has nothing to serve (403). Re-pin all four
BtbN targets to a live tag (autobuild-2026-09-27-13-04, n9.0.2-12) with
digests taken from the GitHub release API.

(cherry picked from commit 91371d0b9a; ffmpeg lock/liveness hunks dropped, superseded by #125468)
2026-09-28 00:58:16 +05:30
Hermes Agent
980318e689 fix(install): detect arch-suffixed desktop builds on repair/upgrade reruns (#94703)
electron-builder names the unpacked output <os>-unpacked on x64 and
<os>-<arch>-unpacked elsewhere (linux-arm64-unpacked, win-arm64-unpacked).
install.sh desktop_product_present and install.ps1
Test-DesktopProductPresent only listed the x64 names, so a rerun on an
ARM64 desktop install skipped the desktop rebuild and left a bundle built
from the previous code. List every unpacked dir main_desktop.py already
resolves, in both installers.
2026-09-26 20:29:47 -05:00
ethernet
746d861504 fix(install): don't ask the gateway install questions twice
The setup stage installs the gateway service through
ensure_gateway_service. On Windows that asks the start-now, Scheduled
Task and UAC questions. The gateway stage then ran `hermes gateway
install`, which asked them all again.

`gateway install --if-missing` does nothing when a service is already
installed. Both installers' gateway stages use it, so they ask only
when setup did not install the service.
2026-09-24 23:49:18 -04:00
ethernet
90963a6f74 install: --skip-browser / -SkipBrowser become PM's persisted opt-out
The flag used to exit 1 as retired. Now that PM installs the browser tools
by default, it maps to `pm.cli install --without agent-browser`, which later
installs and `hermes update` honour.
2026-09-24 17:27:45 -04:00
ethernet
25282d37a7 fix(install.ps1): fast-forward without a diffstat
Updating an old checkout ran `git merge --ff-only`, which prints a
diffstat plus create/delete-mode summary. From v2026.7.1 to today that
is ~27k lines, emitted in under a second. Hermes-Setup.exe forwards
every stage line to its window as its own Tauri event; the burst
overflows the UI thread's Windows posted-message queue (10k), emits fail
with FailedToSendMessage, and afterwards clicking Launch can leave the
installer on "Launching" without ever spawning Hermes.exe.

This is why both Windows desktop-installer@latest E2E legs from
v2026.7.1 timed out waiting for the app window after Launch, while the
same routes from v2026.9.24 (small diff) passed.

Reproduced on Windows arm64 with the production Hermes-Setup.exe and a
protocol-faithful stage script: a 32k-line burst hangs Launch in most
runs (with ~14k FailedToSendMessage warnings), 5k lines and no burst
always launch. --no-stat reduces the merge to two lines.
2026-09-24 16:17:35 -04:00
ethernet
ac3564d25b fix(install.ps1): skip MSYS /proc symlinks when extracting pinned git
The Git-for-Windows archive ships dev/fd, dev/std{in,out,err} and
etc/mtab as symlinks into /proc. Without symlink rights (a standard
user, not elevated, no Developer Mode) inbox bsdtar cannot create them
and exits non-zero, so the bootstrap failed with "failed to extract
pinned git archive" before pm existed.

Exclude exactly the links pm's own extractor skips (extract_tar
git_msys); any other extraction failure, e.g. a truncated archive,
still fails. A test pins the installer's list to pm's skip set.

Install E2E never saw this: GitHub-hosted Windows runners run as an
elevated administrator, which holds SeCreateSymbolicLinkPrivilege.
2026-09-24 14:47:06 -04:00
ethernet
5640ba3e0f fix(install): hermes runs in the installing window; restore reload hint
The PM-clean installers dropped the old "restart your terminal" /
"source ~/.bashrc" closing line, so a fresh install ended on "Run: hermes"
in a shell that could not find it.

install.ps1 now also prepends the bin dir to $env:Path. That variable is
process-wide, so under the documented `irm | iex` path (and `& .\install.ps1`)
the caller's own window resolves `hermes` immediately. When run as a script
file whose inherited PATH lacked the bin dir (e.g. `powershell -File`, a
child that cannot touch its parent), the ladder ends with one arrow line:
restart the terminal, or reload $env:Path from the User and Machine values.
Setup/gateway already launch hermes through the resolved runtime command.

install.sh prints the equivalent "open a new terminal, or run: source <rc>"
line after the ladder when the inherited PATH lacks ~/.local/bin; the
installer is always a child and cannot change its parent's PATH.
2026-09-24 14:23:48 -04:00
ethernet
e96eca7f1b fix(install.ps1): arrow-style output; collapse git/uv/pm/vcpkg noise
Same presentation as install.sh, in the pre-pm installer's ASCII glyphs
(-> [OK] [!] [X]; PS 5.1 reads a BOM-less script as ANSI). Invoke-Logged
wraps the git, uv, pm and source-completion calls: on a console one status
line plus logs\install.log and a failure tail; CI, -Verbose or redirected
output (the -Json stage driver) stream through Out-Host as before. It keeps
Invoke-Native's contract: $LASTEXITCODE stays the caller's to judge, and
nothing reaches the pipeline, so value-returning functions can call it.

windows-build-deps.ps1 runs under pm (installer, hermes update) with the
console as stdout, so the vcpkg clone/bootstrap, the OpenSSL port build
(patch application and all) and rustup get the same status line, logged to
build-tools\build.log; discovery notes print only when streaming. -Verbose
exports HERMES_INSTALL_VERBOSE so that child streams too.
2026-09-24 14:23:48 -04:00
ethernet
2ba873b93a fix(install.ps1): resolve the bootstrap Python once per process
The full ladder runs every stage in one PowerShell process, and venv,
python-deps, source completion and launcher publication each called
Get-BootstrapPython, repeating the uv probe and `uv python find`.
Memoize the resolved interpreter in script scope. The existing
find-before-install order is unchanged.
2026-09-24 14:08:22 -04:00
ethernet
a5f5a31fe7 fix(install): fall back to the mirror when the progress download fails
Invoke-DownloadWithProgress runs Invoke-WebRequest in a separate runspace,
where an HTTP or DNS failure is non-terminating: EndInvoke returned normally,
the caller skipped the mirror, and Get-FileHash died on a file that was never
written. Rethrow the runspace's first error outside the unwrapping catch, so
the caller sees the same WebException/HttpResponseException it classifies.
2026-09-24 12:16:33 -04:00
ethernet
e8d1fc76ec fix(install): run the installed hermes under the Restricted policy
`irm | iex` runs install.ps1 as text, which execution policy never
checks, but Invoke-InstalledHermes then dot-sourced runtime.ps1 from
disk. That is a file load, and the default Restricted policy (Windows
Sandbox, fresh machines) refused it right after "hermes command
installed". Load the helper from its text instead.

That failure hid a second one on the same path: the `$command` local
was shadowed inside Invoke-Native by its case-insensitive `$Command`
parameter, so `& $command[0]` invoked the scriptblock itself until the
call depth overflowed. Rename the local.
2026-09-24 11:56:34 -04:00
ethernet
bcabc5b881 fix(install): keep the PowerShell session open when an iex install fails
The documented one-liner, iex (irm .../install.ps1), runs the installer
inside the user's own session. Fail ended with exit 1, so any failed
stage closed the user's PowerShell window.

Fail now throws. The two entry points own reporting and the exit code:
-Stage prints the reason, emits the -Json frame and exits 1, as before;
the full install exits 1 only when it runs from a script file, and under
iex it prints the reason, sets LASTEXITCODE=1 and returns. A scriptblock
literal's File tells the two apart: $MyInvocation.MyCommand.Path names the
caller's script under iex.
2026-09-24 11:31:08 -04:00
ethernet
f30e86c665 fix(install.ps1): more windows log improvements 2026-09-24 11:21:47 -04:00
ethernet
419a3cbe00 fix(install): request the native bootstrap Python on Windows-on-ARM
A bare version request lets uv pick an emulated x86_64 CPython on
Windows arm64 hosts ("support for the native architecture (aarch64) is
not yet mature"). The bootstrap interpreter then ran as win-amd64.

Request cpython-<minor>-windows-<arch>-none from the machine
architecture the scripts already detect, in install.ps1,
setup-hermes.ps1 and setup-hermes.sh (win32 only; POSIX keeps the bare
version so uv still picks the right libc variant).
2026-09-24 10:58:07 -04:00
ethernet
f39aa8a7a6 feat(install): progress bars on windows 2026-09-24 10:35:19 -04:00
ethernet
f6585964b6 feat(install): windows install progress 2026-09-24 10:32:30 -04:00
ethernet
dcfe8f2d75 fix(install): refuse occupied Windows checkout before Git provisioning 2026-09-24 06:09:17 -04:00
ethernet
fb7fe862ef fix: restore pinned Git in each Windows bootstrap stage 2026-09-24 04:42:28 -04:00
ethernet
cb7b18431a Merge remote-tracking branch 'origin/main' into ethie/pm-clean
# Conflicts:
#	apps/desktop/src/app/settings/connections-registry.tsx
#	scripts/install.ps1
#	scripts/install.sh
#	tests/hermes_cli/test_update_autostash.py
2026-09-24 03:48:31 -04:00
brooklyn!
c7d2985ae3 fix(install): keep dropped commits behind a rescue ref on the installer reset
Re-running install.sh / install.ps1 over an existing checkout (desktop
bootstrap and its update retry do this) falls back to
`reset --hard origin/<branch>` when a fast-forward fails, with no anchor for
the commits it drops. Park HEAD under refs/hermes-update-backups/, the same
namespace `hermes update` writes and prunes, and print the ref.
2026-09-24 02:40:53 -05:00
ethernet
f91bd82286 fix: restore catalog Hindsight and harden installers and test guards 2026-09-24 02:03:59 -04:00
ethernet
c3e9ac87a7 fix(installer): clone treeless instead of full history
A --depth 1 clone hides the release tag runtime identity is derived from
and cannot resolve a non-tip --commit pin or the ancestor guard; a full
clone downloads every tree and blob ever committed. --filter=tree:0 keeps
the whole commit graph and its tags and fetches trees on demand: 125M vs
77M for --depth 1 against GitHub, identity exact. The deferred-checkout
fallback uses the same filter.
2026-09-24 00:51:07 -04:00
ethernet
4e3684c993 fix(install): create the pm store root before publishing pinned git
Prerequisites is the first stage, so on a fresh Windows host
<HermesHome>\tools does not exist when Get-PinnedGit runs, and Move-Item
throws DirectoryNotFoundException (reported against the source path).
The uv stager already creates its slot with New-Item -Force.
2026-09-23 18:10:09 -04:00
ethernet
1038f8479c fix(install): make both installers survive their real invocation paths
Review findings against the pm-clean installers, each reproduced first:

- install.sh: `curl | bash` aborted before main under `set -u` (empty
  BASH_SOURCE). The entry guard falls back to $0.
- install.sh: setup/gateway read stdin, which under `curl | bash` is the
  script itself. They open /dev/tty when a terminal can be opened, and
  otherwise skip with guidance.
- Both: any uv on PATH was trusted. uv 0.6.17 has no `python install
  --no-bin`. A PATH uv now has to run and be at least the pinned version,
  otherwise the pin is staged.
- install.sh: the staged uv went under ~/.hermes/tools even with a custom
  --hermes-home. It now goes to pm's store_root() default,
  $HERMES_HOME/tools.
- Both: when a stash failed, the script logged "overwritten below" and ran
  `reset --hard` anyway. Local work is now parked before checkout, and a
  stash failure stops the install.
- Both: reruns ignored an explicit HERMES_REPO_URL. It now repoints origin.
- Both: --commit had no ancestor guard. The pin must be on the installed
  branch.
- install.sh: the blobless fallback was `--depth 1 --single-branch`, so a
  non-tip --commit could not check out. It now keeps full history with
  blobs fetched on demand.
- Both: ported main's recovery for a commit-less .git (moved aside, #40998)
  and for an unmerged index (reset -q before the stash, #4735). Stashing
  before checkout makes both reachable.
- install.ps1: on Windows PowerShell 5.1, `native 2>$null` / `2>&1` under
  Stop turns stderr into a terminating NativeCommandError, verified on a
  Win11 host. Every native call now goes through Invoke-Native, which
  relaxes the preference only for that call.
- install.ps1: clone publishes from a staging dir, with retries and a
  blobless fallback, and refuses a non-empty destination (mirrors
  install.sh). UV_NO_CONFIG and `--no-registry` are restored. pwsh 7
  HttpRequestException falls back to the mirror, except TLS trust failures.
  tar resolves from System32. A literal CR/LF in the desktop failure
  message is removed.

Deletes six tests that regex-extracted main's legacy install.sh functions
(node, browsers, PATH block, lockfile churn; pm runs `npm ci` whenever a
lockfile exists). The two behaviours still relevant are covered by new
behavioural tests.
2026-09-23 16:28:00 -04:00
ethernet
6cbd72cec1 fix(install): let the documented scriptblock install run install.ps1
`& ([scriptblock]::Create((irm .../install.ps1)))` is the documented
Windows install form, and on this line it failed twice before doing any
work:

- The MSIX rewrite re-added a UTF-8 BOM that had been stripped twice
  before. Windows PowerShell 5.1 irm keeps it as a literal U+FEFF, so
  param( was no longer the first statement: "The assignment expression is
  not valid".
- Initialize-ResolvedPaths read and wrote $script:HermesHome and
  $script:InstallDir. Under -File, script scope is where param() binds.
  Under the scriptblock form param() binds in the scriptblock scope and
  $script: names the caller session, so -HermesHome read as empty and
  Join-Path threw. Without -HermesHome, the normalized paths never reached
  the bare $HermesHome/$InstallDir every stage reads.

The paths are now computed locally and written to scope 1, where param()
bound under -File, the scriptblock form, and dot-sourcing.

Verified on Windows 11 arm64 (PS 5.1): the old file reproduces both
errors, -ShowResolvedPaths is correct under all three entry forms, and a
fresh install clones into the requested home and reaches pm install.
2026-09-23 15:47:32 -04:00
ethernet
3c53fbdc8b install.ps1: emit the -Json failure frame when Fail ends a stage
Fail ends the script with `exit 1`, which unwinds past the stage
dispatcher's try/catch, so the catch that frames failures as JSON never
ran for the installer's own fatal errors: a `-Stage repository -Json`
run whose clone failed printed the reason via Write-Host only and put
zero frames on stdout (verified on Windows: 0 frames before, 1 after).
Only thrown exceptions were framed.

Fail now emits the failure frame itself when running under -Stage -Json,
so every fatal path yields exactly one frame carrying the original
reason, matching install.sh's EXIT-trap framing.
2026-09-21 18:44:30 -04:00
teknium1
d0dbf2cbb6 fix(install): resolve uv shims before salvage and validate the copy in place
Install-Uv accepted any file at $HermesHome\bin\uv.exe, and copied whatever
`Get-Command uv` returned into that location. Chocolatey's bin\uv.exe is a
ShimGen launcher that locates ..\lib\uv\tools\uv.exe RELATIVE to itself, so
the copy is dead on arrival; `& exe --version` does not throw on a nonzero
exit, so the launcher passed the try/catch and the Python stage then failed
with "Python 3.11 not available" (#110350). The re-run path trusted the same
broken copy again.

Building on KoNit-K's Test-ManagedUvBinary and its three call sites:

- Test-ManagedUvBinary merges stderr, relaxes the error preference, and
  returns the `uv <version>` line only on exit 0 -- a launcher's error text
  can no longer surface as "Managed uv found (Cannot find file ...)".
- Resolve-UvShimTarget maps a candidate to the standalone binary before the
  copy: `<name>.shim` sidecar (Scoop), the Chocolatey bin\ -> lib\<pkg>\tools\
  layout, symlinks (winget Links\); other reparse points (WindowsApps
  app-execution aliases) have no copyable file and skip the salvage.
- The salvage rung validates the candidate where it lives, copies, then
  validates the COPY at its new location and removes it on failure, so the
  stage fails honestly instead of reporting success over a dead launcher.
- scripts/tests/test-install-ps1-uv-shim-validation.ps1 drives the real
  Install-Uv with compiled fake uv binaries (a working uv and a
  location-relative launcher) under stubbed installer rungs; wired into
  installer-tests.yml for pwsh 7 and Windows PowerShell 5.1.

Co-authored-by: joaomarcos <joaomarcosdias444@gmail.com>
2026-09-19 02:44:02 -07:00
KoNit-K
3662a1926d fix(install): validate every managed uv acceptance path
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-19 02:44:02 -07:00
KoNit-K
23298685f0 fix(install): reject broken copied uv shims 2026-09-19 02:44:02 -07:00