3068 Commits
Author SHA1 Message Date
Maciej Lisiewski 29e78b124e docs: pnpm commands for the JS workspaces
READMEs, contributor guides, AGENTS.md files, skills and the website (with
the zh-Hans translations) give pnpm commands for the workspaces; the
website itself is still an npm project.
2026-11-06 04:08:45 +01:00
Maciej Lisiewski 7062229090 chore(js): switch the workspaces from npm to pnpm
pnpm 12.15.0 (native executable) installs every JS workspace from one
pnpm-lock.yaml, imported from package-lock.json with the same resolved
versions. website/, the WhatsApp bridge and the photon sidecar stay separate
npm projects.

Configuration moves from .npmrc and the root manifest into
pnpm-workspace.yaml: the 14-day minimumReleaseAge and its excludes,
overrides, allowBuilds (the old allowScripts set, with strictDepBuilds
failing any unlisted build script), engineStrict, sideEffectsCache: false
(in-place rebuilds must not write into the store) and
verifyDepsBeforeRun: false (a `pnpm run` never installs).

The pnpm version is pinned in pm/lock.json and in root devEngines.
pmOnFail: ignore keeps that pin out of pnpm-lock.yaml: a leading pin
document makes osv-scanner read zero packages. scripts/build/node-deps.mjs
enforces the exact version instead, and every lock reader refuses a
multi-document lock. A root preinstall refuses `npm install`; devEngines
onFail is warn so npx (MCP servers, the tsc linter) keeps working inside
the checkout.

User-machine installs run --frozen-lockfile --trust-lockfile (release age is
checked when locking, in CI, not per user). node-deps removes npm-era
node_modules trees before the first pnpm install. Nix builds the store with
fetchPnpmDeps and a hash file that CI checks.
2026-11-06 04:08:24 +01:00
Maciej Lisiewski 215aa7624b fix(ci): end the gate's timing at its verdict, not at the report's steps
close_running_job() counted every finished step of the report's own job. On a PR the PR-only baseline restore finishes before collection, on main it never runs, so a slow cache download inflated the PR's gate time against main's baseline. ci.yaml now passes --own-job-last-step "Evaluate job results" and the gate's time ends there; steps after it are report work.
2026-11-06 03:52:52 +01:00
Maciej LisiewskiandBearHuddleston 7979dc05af fix(ci): skip the running gate's log download; drop an unseeable test
The timing report's own job has no log until it finishes, so fetching it listed the gate under "Unavailable job logs" in every report. Its timing entry stays; only the log download skips it.

test_status_functions_are_wrapped_so_yaml_keeps_the_negation could not see the case it described: ruamel reads an unquoted `if: !cancelled()` as an empty tagged scalar, which test_every_check_runs_whatever_an_earlier_check_did already catches.

Co-authored-by: BearHuddleston <BearHuddleston@users.noreply.github.com>
2026-11-06 03:42:39 +01:00
Maciej LisiewskiandBearHuddleston d2c422f294 fix(ci): name each red step of a failed job in the CI review comment
repo-guards.yml and lint.yml run many checks as steps of one job, so the comment's "Job **X** failed" item no longer said which check went red. split_failed_steps() reports each failed step of a failed job as its own entry, for the comment only: the poller's job counts and transitions stay per job, and a failed job with no failed step (a timeout) keeps its entry. The dependency-bounds failure step carries "supply-chain" in its name so the supply-chain review status still covers it, as the old job name did.

Patch by @BearHuddleston (review of #149786).

Co-authored-by: BearHuddleston <BearHuddleston@users.noreply.github.com>
2026-11-06 03:42:39 +01:00
Maciej Lisiewski 603387951c fix(ci): count the gate job in the timing report it now writes
The timing report moved into the all-checks-pass job, which the jobs API
still lists as in_progress while the report collects. collect_timings
dropped every unfinished job, so the gate vanished from every report and
from main's baseline, and wall time stopped at the last worker.

The job the report runs in (matched by RUNNER_NAME) now counts up to its
last finished step: the verdict, without the report's own steps, as when
the report was a job of its own. Other unfinished jobs stay out.
2026-11-06 02:38:46 +01:00
Maciej Lisiewski d7244125e9 ci: run every cheap repository guard as steps of one job
Thirteen guard workflows (fifteen jobs) ran on the same 4-vCPU Ubuntu
runner, all started right after detect, each paying a checkout and a full
billed minute for 0-13s of work: infographics, profile archives, case
collisions, blob size, lazy_deps imports, stale icons, unrelated
histories, contributor attribution, uv.lock, prompt-surface and
package-lock diffs, the supply-chain scan and dependency bounds, and the
Docker lint.

repo-guards.yml runs them as steps of one job. Each step keeps its old
lane gate in its `if:`, and runs whatever an earlier step did
(`!cancelled()`), so one red guard never hides another; any failed step
fails the job, which all-checks-pass requires. The review-status-*
artifacts the live PR comment reads keep their names. Files the steps
write go under $RUNNER_TEMP, and the guards that need no toolchain run
before setup-pm, whose dependency phase also checks the lock.

The four PR-only jobs the strict release gate tolerated as skips are now
steps of a job that runs on every event, so the only tolerated release
skip left is the deferred Desktop E2E.
2026-11-06 02:15:36 +01:00
unsupportedpastels 0502fe0597 feat(plugins): one published list of what plugins may import
The public list `hermes plugins validate` checks plugin imports against
was a hardcoded frozenset inside hermes_cli/plugin_validate_private_imports.py,
with no page that showed it to plugin authors.

Move the list, unchanged (89 entries), into hermes_cli/data/plugin_api.json
(already shipped as package data via `data/*.json`) and load it once in the
validator. scripts/gen_plugin_api_docs.py renders
website/docs/developer-guide/plugins/public-api.md from the same file, and a
test fails when the page is stale. Validate output is unchanged for the same
inputs.
2026-11-05 18:47:10 -05:00
unsupportedpastels 4c376610a0 docs(gateway): ledger notes the /api/ws heartbeat has no row
gateway.ping is answered by the ws read loop before dispatch and is in no
handler table, so the generator docstring says so.
2026-11-05 17:47:55 -05:00
unsupportedpastels d69fb631db fix(gateway): ledger finds route_on gates by code object, not name
The surface-gate lookup matched the inner function by name and read its
first closure cell, so renaming it or capturing another value ahead of
field silently dropped or misrecorded every surface suffix. It now
compares against route_on's code object and reads the cell named field;
a rename of field fails with KeyError. The regenerated ledger is unchanged
(12 gated routes).
2026-11-05 17:47:55 -05:00
unsupportedpastels b796dc1b7b fix(gateway): ledger keeps account-owned connector calls on legacy fallback
The authority serves connection.respond, connectors.connect and
connectors.operation.status/wake only for a session-owned call; an
account-owned one (the Desktop Connectors page) gets -32601 and ws.py
falls back to the legacy handler. The ledger recorded all four as
authority-owned. They now read "authority (session-owned); account-owned:
legacy fallback" and stay undecided (D3).

The refused set now comes from calling legacy_fallback_allowed with the
interactive capabilities, the same predicate ws.py calls, instead of
re-deriving it from the module's private tables.
2026-11-05 17:47:55 -05:00
unsupportedpastels 0e5ac8012b chore(gateway): keep the serve ledger with its test, not in the docs site
The ledger is working data for the serve cutover, not user documentation, and
is deleted with the serve stack. Move it next to its drift test as
tests/hermes_cli/serve_handler_ledger.json, which Docker, Desktop payloads and
Nix already exclude; the generator and the test only change the path.
2026-11-05 17:47:55 -05:00
unsupportedpastels 1f8b980832 docs(gateway): profile creation stays host-wide in the serve ledger
Creating a profile is host-level work, not session work, so POST /api/profiles
and profiles.create are recorded as keep-host-wide instead of undecided.
2026-11-05 17:47:55 -05:00
unsupportedpastels e71bf329b4 docs(gateway): ledger of every serve handler and who owns it
Add scripts/gen_serve_handler_ledger.py and the generated
website/docs/developer-guide/serve-handler-ledger.json: one row per
HTTP/WebSocket route of the app `hermes serve` runs and per JSON-RPC
method an /api/ws client can call, with its current path and an owner
decision.

RPC rows are classified with the tables /api/ws dispatch reads: the
authority's served verbs, then ws_legacy_fallback's refused/fallback
rules. A served verb is decided `authority`; every other row is
`undecided` (RPC fallback/refused rows reference D3) until a reviewed
decision is recorded in the script's DECISIONS table. Dashboard-plugin
routes depend on installed plugins and are one row, the hot-mount route.

tests/hermes_cli/test_serve_handler_ledger.py fails when a handler is
added or removed without regenerating the ledger; --check does the same
from the command line.

Refs #145171 (EP-1)
2026-11-05 17:47:55 -05:00
Teknium 52f15ab6a9 fix(release): a Store commit answered 202 Accepted counts as committed
Publishing v0.23.0 committed the Microsoft Store submission, and Partner
Center answered POST .../commit with 202 Accepted and status CommitStarted.
The submission API helper only accepted 200, so the publication pass logged
"commit failed: 202 {'status': 'CommitStarted'}" and told the release owner
to check Partner Center by hand, even though the commit had started.

Any 2xx is now success. The fake Partner Center in the tests answers a commit
with 202, as the live API did, so the six tests that commit a submission fail
on the old check.
2026-11-05 13:47:00 -08:00
Teknium 5819226468 feat(plugins): one switch and one action for unattended plugin updates
main already ships `hermes plugins update` (no name = update all) and the
opt-in `plugins.auto_apply` cadence. This makes both reachable from every
surface and closes the gaps reported on the original PR:

- Catalog installs now ride `plugins.auto_apply` too (it moved only git
  rows, so catalog plugins stayed on their install-day pin). The update
  path still refuses a widened pin or a too-new requires_hermes unattended.
- One updater per plugin: a cross-process lock around update_one and the
  dashboard/Desktop update, so the gateway's auto-apply tick and a manual
  update never run on the same plugin at once; the loser changes nothing.
- `hermes update` names, per profile, the catalog installs its new catalog
  pins left behind and re-arms that profile's plugin check, so an opted-in
  gateway applies them on the next tick.
- New CLI verb `hermes plugins auto-update [on|off]`; `/plugins update` and
  `/plugins auto-update [on|off]` in the classic CLI and the TUI (slash
  worker).
- RPC `plugins.manage` actions `update_all` and `auto_update` (contract +
  regenerated apps/shared outputs) for Desktop.

Off by default (legacy behaviour); all writes are profile-scoped through
get_hermes_home()/the scoped RPC.
2026-11-04 21:58:12 -08:00
Maciej Lisiewski aac7f1b696 Merge pull request #148651 from NousResearch/ci/desktop-targets-one-source
refactor(release): define the desktop targets once
2026-11-04 05:41:25 +01:00
unsupportedpastels 07274ac654 fix(release): don't say Publish now for a committed auto-publish Store build
With MS_STORE_HOLD set after a build was committed Immediate, the API
refuses the switch to Manual and the pass told the operator to select
Publish now, though the build goes live by itself once certified. A
refused switch now reads the committed mode: Immediate reports that it
publishes on its own and that the hold cannot apply.
2026-11-03 20:22:40 -08:00
Teknium d6c98faab0 fix(release): MS_STORE_HOLD opts a Store build back into Publish now
Committing the Store draft at publication makes every certified build go
live by itself, with no way left to keep one back for a chosen release time.
Setting the repository variable MS_STORE_HOLD=true makes the publication
pass commit the draft Manual instead: it is certified and then waits for
Publish now in Partner Center, with a warning that says so. A rerun of a
held submission is a no-op and never flips it to Immediate. Unset keeps the
auto-publish default.

Also quote the real v0.22.7 refusal (state 'PreProcessing') and update the
stable-releases developer guide, which still described the held commit.
2026-11-03 20:22:40 -08:00
unsupportedpastels b7ed4972da fix(release): commit the Store draft at publication so it goes live once certified
The green run committed the Store submission held (Manual) and the
publication pass tried to switch it to Immediate. Partner Center refuses
to update a committed submission (v0.22.7: 409 InvalidState, "Cannot
update the submission because it is in certification"), so every
release needed Publish now by hand.

stable-store now leaves an uncommitted draft set to Immediate, and the
publication pass commits it. Nothing reaches certification or customers
before the release is published, and the build goes live as soon as it
passes certification.
2026-11-03 20:22:40 -08:00
Maciej Lisiewski 79c1648300 refactor(release): define the desktop targets once
scripts/releases/desktop_targets.py holds the (electron platform, arch)
triple and derives the manifest spelling and the native handoff legs from it.
channel_artifacts, channel_publish, channel_releases, stable and the builds
table read it instead of five hand-written copies, and NATIVE_LEGS has one
definition instead of two.

read_native_receipts always reads every native leg, so its legs parameter
and _leg_targets go. A claim with includeMacosIntel: true now says why it is
refused. Tests iterate the targets directly instead of skipping a 2x2 case.
2026-11-03 23:54:59 +01:00
Maciej Lisiewski 7b55a7c236 Merge pull request #148622 from NousResearch/ci/drop-macos-intel
ci: remove the Intel macOS CI and release legs
2026-11-03 21:56:01 +01:00
Maciej Lisiewski 8f4ac1c1da Merge pull request #148630 from NousResearch/deps/security-batch-2026-11-03
fix(deps): clear release-age-eligible security alerts (undici, zod, fast-uri, qs, pypdf, pyasn1, tornado, filelock, setuptools)
2026-11-03 21:52:27 +01:00
Maciej Lisiewski e339188e89 fix(deps): bump body-parser to 1.20.9
Moves the transitive body-parser copy (express ~1.20.5) from 1.20.8 to
1.20.9 in the website lock, and the whatsapp-bridge override pin that
holds it there. 1.20.9 was published 2026-10-22, inside the website's
14-day npm release-age gate, so website/.npmrc gets a dated
min-release-age-exclude entry. Remove it once 1.20.9 clears the gate
(2026-11-05). whatsapp-bridge has no release-age gate.

Advisory: GHSA-27r3-jxmh-p828 (high, DoS via trailing bytes after a
compressed body)
2026-11-03 21:39:10 +01:00
kshitijk4poor 83d635f21f fix(runner): no test file sees HERMES_TEST_FORKSERVER, the first ones included
Review (BearHuddleston): _run_one_file_once copies os.environ before it calls
start_pytest(), which is where the first file pops the knob. So the first
file, and any others in the opening wave, still carried it: 1-3 files of 200
at -j 32 in every run. A test that starts the runner or run_tests.sh from one
of those files would silently run its nested suite under the fork server,
and _longest_first now puts the slowest cached files, the runner's own tests
among them, at the front of each slice.

start_pytest drops the knob from every file's env, on both launch paths. The
parity probe's first file asserts the knob is absent (it saw '' before), and a
24-file opening wave at -j 12 asserts no file sees it.
2026-11-04 02:01:54 +05:30
kshitijk4poor 23eaee2f17 ci(tests-os): say so when the PowerShell warm-up could not save its cache
Review: if the cache file was never rewritten within the 20s wait, the step
still printed 'module analysis cache ready'. It now prints a warning with the
cache path instead; still non-fatal.
2026-11-04 02:01:54 +05:30
kshitijk4poor ddd63dde0d refactor(runner): both ways to start a file live in run_tests_launch.py
Review: the fork mode spread its server state, lock, process creation and
process-group rules through the runner, so _run_one_file_once had to know that
a forked child calls setsid() while a subprocess needs getpgid(). The runner
now calls start_pytest() and gets the process and its group back; the fork
server, its lock and the Popen shim live with it. Behaviour is unchanged.

classify_changes lists the launch module and the fork child as test harness
next to run_tests_parallel.py, so editing either starts the lanes a runner
edit starts (the fork child was missing since it was added).
2026-11-04 02:01:54 +05:30
kshitijk4poor 1d2c2a5d28 fix(runner): a forked file is a main process to multiprocessing; review cleanups
Under the fork server multiprocessing still saw each file as the server's
child: current_process() was ForkServerProcess-N with a parent, every file
after the first inherited the runner's /tmp/pymp-* temp dir, and __main__ was
this runner, re-run in every child as __mp_main__ (~7-20ms per file). A
test's own Process, Pool or spawn context now sees what python -m pytest gives
it: a parentless MainProcess, a temp dir under its TMPDIR, and pytest as
__main__ and __mp_main__; the server preloads the runner once. The fork probe
compares these against a fresh pytest process, from the server's second child.

Also from review: run_tests_fork_child.run takes keyword arguments and one
argv; the start-under-lock is one helper the race test calls, and the test
starts 2000 children (unlocked, ~1 status in 400 comes back 255), so dropping
either lock fails it; kill() leaves a reaped pid alone; the output file is
removed when start() fails; the exit-status note skips exit 1 (tests failed,
already reported) and crashes; the 2.0s default duration is one constant; the
new tests reuse the file's runner helpers. The docstring records the gap left:
a daemon a test forks without exec escapes the leftover sweep.
2026-11-04 02:01:54 +05:30
kshitijk4poor 22207eb936 fix(runner): start the fork server on first use; satisfy the lint lanes
main() is over the code-health ratchet's complexity and length budgets, and
starting the fork server there grew both. The first file now starts it under
the fork lock, before it copies os.environ, so the knob still never reaches a
test. Also: a timeout on the sys.path probe (HX006), a utf-8-sig read of the
child's output file, an unquoted annotation (UP037), and the Linux-only setsid
marked for the Windows footgun check.
2026-11-04 02:01:54 +05:30
kshitijk4poor bf1de8beff ci(tests-os): warm PowerShell's module analysis cache before the Windows tests
Forwarding PSModuleAnalysisCachePath only helps when something already warmed
the cache. Whether the ARM64 build-deps step did depended on how long its
PowerShell stayed up: PowerShell writes the analysis from a timer ~10s after it
runs (AnalysisCache.QueueSerialization), so a process that exits right after
its first cmdlet never saves it. On runs where build-deps finished fast the
cache stayed cold and every PowerShell child a test spawned paid ~22s again
(arm64 test step 206-259s instead of ~130-150s).

Warm it explicitly from a cmd step and stay up until the cache file is
rewritten: ~39s when cold, ~1s when warm. Forced-cold check: a child launched
afterwards started in 0.4s and the test steps ran in 137/146s.
2026-11-04 02:01:54 +05:30
kshitijk4poor 17db2a8e33 ci(setup-pm): keep the cached Git entry valid on Windows
Git for Windows ships etc/mtab as a Cygwin system-file symlink, and PM's digest
covers those 38 bytes. @actions/cache restores the PM store with Git's MSYS tar
under MSYS=winsymlinks:nativestrict, which writes a symlink to a non-Windows
target (/proc/mounts) as a WSL reparse point (tag 0xa000001d). Python cannot
open that, tree_digest raised, PM's verification of the cached Git entry
failed, and every Windows job re-extracted the 56 MiB PortableGit archive.

Before PM verifies the store, recreate each WSL symlink with Git's own ln under
winsymlinks:sys. PM's check still decides whether the entry is intact.
2026-11-04 02:01:54 +05:30
kshitijk4poor 3c815f1b9a fix(runner): read a forked file's exit status under the fork lock
multiprocessing's Process.start() polls every live child
(multiprocessing.process._cleanup). With the fork server, a child's exit
status is 8 bytes the server writes once on the child's status pipe. When a
worker thread starting the next file polled a child at the moment that child's
own thread called join(), one read got the status and the other got EOF, which
multiprocessing reports as exit status 255. A green file then failed with no
error (CI: tests/agent/test_openrouter_silent_default_purge.py, 5 passed, exit
255). Stress repro: 283 of 3000 children reported 255; 0 with this change.

Wait on the sentinel without reading, then join and read exitcode while holding
the lock that also serialises start().
2026-11-04 02:01:54 +05:30
kshitijk4poor 613f548676 fix(runner): say how a green file's pytest process exited
A file can pass every test and still exit non-zero. Its failure dump showed
only the green pytest summary, so the cause (an exit-time hook, a status set in
sessionfinish, a signal) had to be rediscovered by rerunning the whole suite.
Append the child's exit status to the captured output.
2026-11-04 02:01:54 +05:30
kshitijk4poor 904418490c perf(tests): start the slowest cached test files first
The pool starts files in submission order, which was path order, so a slow
file late in the alphabet (tests/tui_gateway, ~40s each) ran alone at the end
of a slice while the other workers sat idle. Submit by cached duration,
longest first; the cache is already restored on CI for the timeout scaler.
2026-11-04 02:01:54 +05:30
kshitijk4poor 5de2f3038d test(windows): forward PSModuleAnalysisCachePath to the test processes
The hosted Windows images point PowerShell at a shared module analysis cache,
which the job's earlier PowerShell steps (the ARM64 build-deps setup) warm.
run_tests.sh starts the suite from env -i and did not forward the variable, so
every powershell.exe a test spawned fell back to a cold per-user cache and
re-analysed every installed module (Az, AWS, Graph): about 22s per launch on the
arm64 runners. The three slowest Windows files (150-167s each) were almost all
that wait.
2026-11-04 02:01:54 +05:30
kshitijk4poor 4ca478075f perf(tests): fork each Linux test file from a server that preloaded pytest
A fresh python -m pytest <file> spends about half a second starting the
interpreter and importing pytest and the third-party libraries nearly every
test file loads; over ~7000 files that is most of an hour of CPU per run.
With HERMES_TEST_FORKSERVER=1 (Linux CI) the runner starts one
multiprocessing fork server that imports those once and forks a child per
file. Each child still starts from a clean state: its own session, the
file's environment, python -m pytest's sys.path and argv, combined output and
pytest's exit code. Product code and tests/conftest.py are never preloaded.
2026-11-04 02:01:54 +05:30
Maciej Lisiewski 36f9cba878 fix(deps): bump setuptools to 84.1.0
Moves every setuptools pin from 83.0.0 to 84.1.0 together: the dev
group, [build-system].requires, and the Termux toolchain and
deb-wheelhouse pins (scripts/termux), so source builds, the lock and
the Termux payload keep using one version. Only setuptools moves in the
relock. 84.1.0 was published 2026-10-19, past the 3-day release-age
check.

This is a major bump. 84.0's removals are distutils compiler internals
(Compiler.spawn shims, exception classes, LooseVersion in
CygwinCCompiler). Hermes's setup.py and the setuptools.build_meta
backend use none of them.

Advisory: GHSA-xmj3-9gf7-h52w
2026-11-03 21:06:30 +01:00
Maciej Lisiewski d7f5c68ab6 fix(deps): bump qs to 6.16.1
Moves the transitive qs copy (via express and body-parser, both ~6.16.0)
in the website and whatsapp-bridge locks from 6.16.0 to 6.16.1.
6.16.2 is still inside the 14-day npm release-age gate (published
2026-10-27); 6.16.1 (2026-10-14) clears it and fixes the advisory.

Advisory: GHSA-6pq8-jhm2-2vcw
2026-11-03 21:06:29 +01:00
Maciej Lisiewski 06192b894c ci(release): stop building and publishing macOS Intel desktop bundles
Remove the darwin-x64 build, receipt and smoke jobs from Desktop Bundled
Release and the candidates/transitions/packaged-x64 jobs from Stable Release,
with the release scripts that required them: the macos-intel lane, the
darwin-x64 job group, the x64 macOS target in candidate/channel manifests and
the two-leg macOS feed merge. The macOS updater feed is arm64-only.

Pre-lanes claims still read: the published ones all carry
includeMacosIntel:false; a claim with true is now refused.
2026-11-03 20:58:05 +01:00
Teknium 114b5cd633 fix(install): accept a suffixless git-remote-https in the readiness check
pwsh-driven installer tests on Linux plant the system git, whose exec-path holds git-remote-https without .exe; the check called that git incomplete and tried to reinstall it.
2026-11-02 20:41:16 -08:00
Teknium d499460634 fix(install): leave the store untouched until the git download verifies
Only the extract dir moves into the store root; the self-extractor still downloads to %TEMP%. Rejected bytes create no tools dir again (test_bootstrap_mirror::test_windows_bootstrap_rejects_corrupt_bytes_before_extract).
2026-11-02 20:41:16 -08:00
Teknium 78927702bf fix(install): move the staged git as one rename so a held file cannot split it
Windows PowerShell's Move-Item falls back to a file-by-file copy when a
directory rename is refused. When a file is held without delete sharing
(Defender scanning freshly extracted files, a lingering post-install
child), the copy stops at that file. cmd\git.exe has already landed in the
store by then and git-remote-https has not: this is the broken tree from the
Discord report. A windows-latest probe reproduces it on both same-volume
and cross-volume moves.

Stage the extract beside the entry under the store root (.staging-*, the
prefix pm gc already reclaims), so the last step is a single
[IO.Directory]::Move on one volume that moves all or nothing. Retry
moves and deletes refused by a temporary hold (1+2+4+8+16 s, like pm's
Store.publish). A hold that never clears now fails with a message
naming the folder.
2026-11-02 20:41:16 -08:00
xxxigm 9976c9b04d fix(install): replace a pinned git that cannot clone over https
A sharing violation during extract can leave cmd\git.exe in the store without git-remote-https. The next run treated that file as installed, and every https clone then died with "remote helper 'https' aborted".
2026-11-02 20:41:16 -08:00
Bear Huddleston 2dbeda872f Merge pull request #148193 from NousResearch/fix/pm-uv-cache-keep-symlinks
fix(bundles): keep uv's wheel links from the payload cache to the machine cache
2026-11-02 21:51:10 -06:00
BearHuddleston cd5cfcd192 fix(bundles): refuse a macOS update ZIP with entries lacking owner write
Squirrel.Mac's ShipIt runs removexattr on every enumerated item of a
downloaded update before swapping it in, and aborts the whole install on
any errno except ENOATTR. A read-only file or directory the caller owns
returns EACCES, so one such entry wedges every macOS update (#138659).

record --platform macos already opens the update ZIP in every release
path that publishes a macOS feed (stable candidate, bundled canary,
channel build). It now refuses the ZIP when any entry with a Unix mode,
other than a symlink, lacks owner write, naming the count and the first
three paths. Entries without a Unix mode are skipped.

The check is the one @unsupportedpastels suggested in review of #148192;
@vadimcomanescu proposed a build check like it in #138659.

Refs #138659
2026-11-03 02:52:07 +00:00
BearHuddleston 22c7561a72 docs(bundles): state the baked-pyc guarantee for default imports only
The comments said writes to the baked pycs "still cannot land" and that
the macOS signature "never observes a pyc change". That holds for
default imports, which is all the launcher does. It does not hold in
general: with unchecked-hash pycs baked read-only (0444), a stale source
and Python 3.14.9, `--check-hash-based-pycs always` replaced the pyc (new
inode, mode 664), and `-O` wrote new opt-1 pycs beside it. CPython
replaces a pyc through its writable __pycache__ directory, so the
read-only bit never blocked either.

Say what actually keeps default imports off the pycs (unchecked-hash and
the asserted coverage), and note that 0o644 also stops a baked tree from
blocking the next prepare's rmtree on Windows (#137717).

Reported in review by @unsupportedpastels.
2026-11-03 01:17:31 +00:00
BearHuddleston 7dc7e648ce docs(bundles): scope the uv wheel-link note to macOS and Linux
The shared-bundle-builds note and the stage_uv_cache docstring read as if
uv's wheel entries were symlinks on every target. On Windows uv writes
pointer files. The docstring's layout also covered only the pypi family;
URL, git and named-index entries carry an extra hash directory.

Reported in review by @unsupportedpastels.
2026-11-03 01:16:49 +00:00
BearHuddleston b8e6687e63 fix(bundles): ship uv's wheel links as links in the payload cache
stage_uv_cache copied the build cache with shutil.copytree's default
symlinks=False. uv stores each cached wheel as a relative link into
archive-v0/, so the copy stored every unpacked wheel a second time and
the seed then merged those directories into the machine cache. On the
published v0.22.0 macOS ZIP that is 30,859 files under
uv-cache/wheels-v6 (278 MB compressed, 901 MB installed), and uv fails
with "Is a directory (os error 21)" when it publishes one of those
wheels again (#141891). Windows is unaffected: uv writes pointer files
there.

Keep the links. The signing walkers already skip symlinks, so each
binary is still signed once at its archive-v0 path. Also correct the
shared-bundle-builds doc and the staging comment: built wheel ZIPs ship.
2026-11-02 23:12:45 +00:00
liuhao1024 6e8a0deb82 fix(bundles): bake pycs owner-writable so Squirrel.Mac updates install
ShipIt strips the quarantine attribute off every file in the .app and
aborts the whole update with EACCES on any read-only one, so a payload
whose baked pyc files were sealed 0444 (f6713f2762) could never pass
a macOS update: three attempts, "Couldn't remove quarantine attribute",
then "Too many attempts to install, aborting update" and the old app
relaunches on the old version. Bake them 0644 instead: unchecked-hash
pycs are never rewritten and asserted coverage means no cache-miss
write can target them, so the read-only bit only ever blocked the
updater.

(cherry picked from commit fa34f089cb6e05bc86c03320eaf0d5e136b96f7d)
2026-11-02 23:12:44 +00:00
Teknium debdab9fec fix(desktop): drop python-docx's unzipped template from the Windows payload so MakeAppx can pack it
The signed Windows MSIX build fails every run since the [documents] extra
(b357ebbfc4) put python-docx into the bundled payload: its wheel carries
docx/templates/default-docx-template/[Content_Types].xml, and MakeAppx
refuses that package-reserved name anywhere in the layout with 0x8007007b
"The filename, directory name, or volume label syntax is incorrect".

The directory is the unzipped source of templates/default.docx; python-docx
only ever opens the .docx, so dropping the tree from the win32 venv and the
shipped uv-cache keeps Document() working. Any other reserved-name file now
fails payload staging with its path instead of failing the pack anonymously.
2026-11-02 02:02:44 -08:00