Commit Graph

46099 Commits

Author SHA1 Message Date
Siddharth Balyan
fc042f1d67 refactor(desktop): route tool cards and bridge events through lookup tables (#127828)
* refactor(desktop): route tool cards through a table keyed on the card-tool names

The name-keyed if ladder in ChainToolFallback becomes TOOL_CARDS, typed
Record<CardToolName, FC>. CardToolName comes from the same list isCardTool
reads, so a card added to one list and not the other fails the build.
Behaviour is unchanged.

* refactor(desktop): dispatch desktop bridge events through a handler table

The six-branch event.type ladder becomes DESKTOP_BRIDGE_HANDLERS, one row
per event with its body and comments unchanged. Behaviour is unchanged.

* chore: retrigger CI (zero-job dispatch failure, auto-heal)
2026-09-29 18:37:11 +05:30
Siddharth Balyan
5eea87882a Clarify: one question shape and one result shape on every surface (skip, cancel, timeout and undelivered are told apart) (#127760)
* refactor(desktop): split clarify-tool.tsx into a clarify/ folder

Pure moves, no behaviour change. Parsing, the question-card core
(shell, choice rows, question block), the delivery watchdog, and each
pending/settled card get their own file so the core can be reused.

* feat(clarify): one questions[] shape with per-question status and one outcome

The clarify tool now takes only questions=[{question, choices?, multi_select?}].
A wrong shape is a tool error that names the right one, so a model that
sends the old top-level question/choices corrects itself on the next call.

Every result has the same shape on every surface: each response carries
status (answered, skipped, unanswered) with user_response null unless
answered, and the result carries one outcome (submitted, cancelled,
timed_out, undelivered) plus an optional surface-written notice. The
timeout and cancel sentences that used to pose as the user's answer are
gone, and the compressor reads status instead of matching their prefixes.

The callback contract is callback(questions) -> {answers, outcome, notice?}
(None = skipped, missing qid = unanswered). tui_gateway settles a batch the
same way for the last lock, a cancel, an interrupt and the deadline, and
clarify.lock keeps a null answer as a skip. A multi-select answer that is
not a JSON array counts as one typed ("Other") answer. The tool-row preview
reads the first question.

* feat(clarify): every surface asks through the one question card

CLI: the batch panel is the only panel; Enter on an empty field skips a
question, Ctrl+C cancels and keeps the locked answers, the deadline returns
timed_out. -q and -z return undelivered with a no-user notice.

Ink TUI: the single-question prompt is gone; the card supports multi-select
(Space or a digit toggles, Enter locks, typed Other text joins the picks),
an empty submit skips, Esc cancels, and "Batch" names are dropped.

Desktop: the single-question card is deleted and its keyboard handling moved
into the one card (arrows, letters and digits, auto-advance, Enter picks then
confirms). Confirm enables at one answer; blank questions lock null; Skip and
a composer message cancel; the settled card shows No answer for unanswered
questions. The bots room card follows the same contract.

Messaging: one card per question with a "Reply skip to skip" line in every
locale; timeouts and delivery failures map to timed_out and undelivered.

* fix(clarify): cancelled for a released messaging card, labels win over the skip word

A prose reply to a messaging choice card, /new and session end released
the wait with "", which read as timed_out. They now resolve with a
CANCELLED marker and the tool gets outcome cancelled.

A typed reply that matches a choice label ("Skip") now resolves to that
choice; the skip word applies only when no choice matches.

The bots room card sends the picked labels as they are; the tool already
strips the recommendation label. Unused OUTCOMES and a new docstring and
comment are removed.

* fix(tui): re-editing a multi-select answer keeps its picks

The Ink card stored a multi-select answer as display text ("A, B"), so
revisiting the question put the whole string into Other and re-locked it
as one item. The answers map now keeps the raw JSON array, the card
restores the picks on revisit, and only the display lines format it.

Comments that earlier edits reworded are restored to their original
words, minus the phrases the change made wrong. The compute-host clarify
lock accepts None for a skipped question.

* test(clarify): align three checks with the one-answer confirm and the regenerated keys

The desktop Cmd+Enter test now expects a send with one answer (the blank
question locks null), the compressor test expects the single-answer summary
the code gives, and locales/_keys.desktop.json is regenerated for the
removed and added clarify strings.

* test(tui): the one-question card header is singular
2026-09-29 18:33:44 +05:30
Siddharth Balyan
bc7f58f3b0 fix(gateway): first-contact note only in DMs, offer flag saved in the routed profile (#127818) 2026-09-29 12:59:23 +00:00
FroRaut
b8a8be185d fix(debug): mask URL tokens and passwords in debug share uploads
hermes debug share, gateway /debug, the dashboard share and the desktop
diagnostics upload sent credential query parameters (?token=, ?api_key=,
?access_token=) and user:pass@ userinfo from logged URLs unchanged, to
the paste service or to Nous storage. Log lines keep these values
because log-time redaction leaves web URLs alone, and Firecrawl, for
example, logs the full URL it scrapes at INFO.

The cause is hermes_cli/debug.py:_redact_log_text, the one redactor on
every share path. It called redact_sensitive_text(force=True) without
redact_url_credentials=True, so the strict URL pass that compaction,
memory context, cron errors and the kanban notifier already use never
ran on the upload copy.

Pass redact_url_credentials=True there. Parameter names, hosts and
paths stay readable; values of credential-named parameters and URL
userinfo become ***.

(cherry picked from commit f522171b836bfd842354f5a34c8c2bdd46b39467)
2026-09-29 07:51:44 -04:00
kshitijk4poor
5ed979bc30 test(gateway): kill live lock holders by captured identity, tolerate AccessDenied
Review follow-ups on the live strict-identity tests:

- keep the holder as the psutil.Process captured at spawn, so a PID
  recycled before class teardown is refused instead of killed;
- suppress psutil.Error (AccessDenied on an exiting Windows process),
  not only NoSuchProcess, so one failed kill cannot leave the rest of
  the tree alive and wedge communicate();
- bound the handshake at 20s (a normal start takes ~1s);
- garble the lock in binary mode like the restore does, and say why
  overwriting without truncating is safe;
- drop spawns_gateway_lookalike from the non-gateway holder test (its
  argv carries no `gateway run`) and note that the class holder spawns
  before the per-test guard is armed.
2026-09-29 17:15:05 +05:30
kshitijk4poor
e4e16a54ae test(gateway): bound and reap the live strict-identity lock holders
The live lock-holder tests could hang or leak:

- the handshake readline had no deadline and stderr went to DEVNULL,
  so a child stuck on import blocked the worker with no diagnostics;
- the holder slept 120s, outliving a killed pytest while holding
  gateway.lock;
- `assert acquire_gateway_runtime_lock()` vanishes under -O;
- kill() hit only the Windows venv launcher, never the self-reported
  interpreter, and pipes were never closed (ResourceWarning per test);
- the discovery test relied on list_profiles() resolving to the temp
  HERMES_HOME and compared unresolved paths (/var vs /private/var on
  macOS, 8.3 names on Windows).

The holder now blocks on stdin, reports failure via exit + captured
stderr, and teardown kills the launcher tree plus the reported PID and
reaps via communicate(). The four class tests share one class-scoped
holder with per-test restore of gateway.pid / gateway.lock (5 spawns ->
2), and discovery pins the profile roots like the named-pipe live test.

Also read gateway.pid with utf-8-sig (windows-footguns lane).
2026-09-29 17:15:05 +05:30
emozilla
5d1ab29b03 fix(gateway): identify a live gateway from its lock when gateway.pid is gone
hermes update on Windows maps running gateways to profiles through
get_running_pid_identity_strict, which raised "active gateway lock has no
PID metadata" whenever the lock was held but gateway.pid was missing. The
whole update aborted before touching anything.

That state is reachable on Windows: status cleanup unlinks gateway.pid and
gateway.lock together, and the lock unlink fails silently while the live
gateway holds the file open, so only gateway.pid disappears (#96582).

The gateway writes the same identity record into the lock when it acquires
it. The strict probe now takes the PID from the lock record, requires a
PID file that exists to agree with it, and runs every existing liveness,
start-time and command-line check against what is present. A garbled lock,
a disagreeing PID file, or a lock holder that is not a gateway still aborts.

(cherry picked from commit 1d6bfbbf94605fcc4c84cb52636887e1e8ae36c1)
2026-09-29 17:15:05 +05:30
kshitijk4poor
d9f6a844c1 refactor(update): print the owed-tail notice once, in the process that boots
A supervised start that also relaunches onto the store interpreter printed the
notice in both processes; it now prints only after the relaunch decision.
The _supervised_child docstring claimed the neighbouring truthy set, which it
does not match, and prepare_launch said a supervised child "never" retries the
tail although stale dependencies still do; both reworded. The supervised test
also pins a clean stdout and a single notice.
2026-09-29 17:05:00 +05:30
kshitijk4poor
a91a24ccdf fix(update): a supervised start says when it leaves the update tail owed
The supervised exemption keeps the pending marker for `hermes update`, but
nothing reported it: a headless host running only the gateway would keep
the unfinished tail (products, skills sync, config migration) indefinitely.
One stderr line per start lands in the unit's journal; stdout stays clean
for commands that emit machine-readable output.
2026-09-29 17:05:00 +05:30
kshitijk4poor
fcf1ea4b99 test(update): pin the supervised-child exemption by behaviour, two tests
Fold the marker off-value table into the supervised-launch test so 0/false
are proven to still repay the tail through prepare_launch, instead of a
separate test of the private parser. Keeps the stale-dependency sync test.
2026-09-29 17:05:00 +05:30
liuhao1024
69f8b703fc fix(update): narrow the supervised-child exemption to the sticky tail
Review follow-up: the early-bail also skipped the one-shot stale-dependency
sync, so a supervised child on an out-of-date tree booted in place (or
crash-looped under Restart=always) instead of syncing and relaunching.
Gate only the pending-tail half, and parse the markers with the repo's
truthy set so 0/false do not suppress anything.

(cherry picked from commit 0838ce2a916faee5fe7f6a6fa30db3d0a6146647)
2026-09-29 17:05:00 +05:30
liuhao1024
8d22a824ff fix(update): keep supervised launches off the pending completion tail
A supervised child (systemd unit, launchd plist, Windows task, s6 — all
export HERMES_SUPERVISED_CHILD or HERMES_S6_SUPERVISED_CHILD) is
relaunched on every manager start. prepare_launch() treats the
source-completion-pending marker as a debt to repay immediately, so a
sticky marker re-runs the completion tail (and its environment builds)
on every boot until the disk fills (#123340). Skip the launch-time
repair for launcher-marked children; the tail stays the CLI's to finish
via hermes update / hermes pm install. INVOCATION_ID is deliberately
not read: systemd exports it to every descendant, so an ordinary hermes
command inside a CI runner would skip a legitimate repair.

(cherry picked from commit 4203c068b8b2df5f6b74b4ea610de15bfa2ef54a)
2026-09-29 17:05:00 +05:30
kshitijk4poor
914d4fcaf5 test(update): pin the handoff sentinel to the real current entrypoints
The sentinel local is never read, so a well-meaning unused-variable
cleanup could delete it and silently re-arm the mid-update takeover.
Assert it in co_varnames of update_cmd._cmd_update_impl and
main.cmd_update (unwrapping update_receipt_scope).
2026-09-29 16:47:48 +05:30
kshitijk4poor
db17e07389 fix(update): mark current updater frames with a sentinel, not missing locals
The pre_update_version co_varnames check added in af26acab73 rejected the
current updater's entrypoints, but it also rejected historical updaters
from 2026-07-26..08-16, whose _cmd_update_impl/cmd_update never declared
that local — their in-process dependency installs would have continued on
the old interpreter instead of handing off to the takeover child.

Invert the gate: the CURRENT updater declares the
_hermes_current_updater_frame sentinel local in both entrypoints; no
historical on-disk version can contain it. in_historical_update() now
hands off on entrypoint match unless the sentinel is present, restoring
the handoff for every historical window while still refusing current
frames. Verified live for all four frame classes; mutation check: with
the sentinel check removed, the current-updater refusal test fails.
2026-09-29 16:47:48 +05:30
kshitijk4poor
09581cacaa refactor(desktop): gate the HUD bridge sentence on tool availability, not note text
Pick the tools the bridge sentence names from the same availability
conditions that gate the note's sentences instead of substring-searching
the rendered prose, and assert the bridge clause by tool membership
rather than exact wording (simplify-code quality review).
2026-09-29 16:40:36 +05:30
kshitijk4poor
312f6bc2fd fix(desktop): HUD note fires again when tool search defers the desktop tools
Since #97979 put the desktop GUI tools on the default tool_search defer
list, a Desktop session's valid_tool_names no longer contains
read_window_below (it sits behind the tool_call bridge), so
hud_surface_note() withheld the whole note on every HUD turn: the model
stopped being told that "this"/"here" means the app behind the HUD.

hud_surface_note() now takes the session's deferred-but-callable names
(the same agent/tool_executor._tool_search_scoped_names set the tool_call
dispatch validates against) and counts them as available, adding one
sentence telling the model to call the deferred ones through the
tool_call bridge — a direct call to a deferred name is rejected as an
unknown tool. The defer list, its token saving and the note text for
non-deferred sessions are unchanged.
2026-09-29 16:40:36 +05:30
hermes-seaeye[bot]
6ffe3b2b59 fmt(js): npm run fix on merge (#127730)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-29 10:43:40 +00:00
kshitijk4poor
a042567296 test: tighten the audit's new assertions after review
- relay-deliver-budget: the ceiling/timeout composition is checked
  against the imported constants (which the mirror tests already tie to
  the Python defaults) instead of re-reading config_defaults.py.
- terminal env sync: the key recorder also traps get()/[] and is an
  instance, the probe fails loudly if the bridge looks up no keys (so the
  gateway-only-key check cannot go vacuous), one _cli_env_map() helper
  serves both tests, and the docstring says what is actually driven.
- live_system_guard bypass reuses _live_system_guard_is_active().
- retaindb no-client prefetch asserts no thread is spawned through the
  module's spawn_context_thread seam, not a private list.

Each changed assertion re-proven red by its production mutation.
2026-09-29 15:58:55 +05:30
kshitijk4poor
34a5f95a30 test: restore five contracts the preservation review found uncovered
An independent review of every deleted test against its named keeper
found contracts whose only proof had gone, or that no test had ever
pinned. Each restoration is one assertion in an existing test, proven red
by the production mutation named:

- gateway /model reply shows the context length the async resolver
  returns (the deleted async-matches-sync test was its only check);
  red when resolve_display_context_length_async drops the value.
- parse_model_switch_args("sonnet") has scope "default" (the deleted
  tuple test covered the no-flag case).
- shared metrics runtime sends nothing when collection is on but send is
  off; red when the gate reads resolved.enabled instead of resolved.send.
- the terminal env-sync test also probes every key gateway.run's bridge
  looks up, so a gateway-only key (which the old source parse caught)
  still fails it.
- desktop: the deliver ceiling equals the backend's lock wait plus every
  attempt, read from the Python defaults.

Also: the tools/bot_relay.py mirror comment names relay-budget.ts, and a
comment orphaned by the Windows encoding-test deletion goes with it.
2026-09-29 15:58:55 +05:30
kshitijk4poor
7e6b8d9ec8 test(desktop): pin the bot_relay.deliver budget by import, not by regexing relay.ts
relay-deliver-budget.test.ts read relay.ts as text: it regexed each
constant ("must stay a literal so this test can read it") and matched the
deliver call shape within 400 characters of 'bot_relay.deliver', which
AGENTS.md bans. The regex passes with the identifier wired into the wrong
argument and fails on a correct refactor.

The #93911 budget constants move to relay-budget.ts and are imported by
relay.ts and both tests. The mirror checks against the Python defaults are
unchanged. The call-site check becomes a behavioural one in relay.test.ts:
the mocked requestProfile for bot_relay.deliver must receive
RELAY_DELIVER_TIMEOUT_MS (red when the call passes any other deadline).
2026-09-29 15:58:55 +05:30
kshitijk4poor
93a7267b10 test: fold private-predicate and replayed-contract tests into their keepers
Each removed test re-asserts a contract a stronger test already owns at
the real boundary; the keeper is named per group.

- api_server CORS: `_origin_allowed` for an allowlisted origin and for an
  Origin-less client -> the configured-origin preflight test and every
  Origin-less /health request. The Idempotency-Key allow-header assertion
  moves into the preflight test instead of running a second identical
  preflight.
- api_server room grants: the execution-policy predicate's two outcomes ->
  test_api_server_runs' HTTP grant-refresh tests (403 on drift, 200 on the
  authorized digest).
- api_server multimodal: the pure input_image canonicalization -> the
  /v1/responses test that sends the same parts and asserts the same shape.
- credential pool: matched-hint rotation (401) and second-429 rotation ->
  test_credential_pool_routing's real-pool hint test and TestPoolRotationCycle,
  which assert the same steps plus the exact rotate kwargs and swap.
- shared metrics: two runtime cases that replay the send resolver's truth
  table -> TestSendRequiresCollection; the e2e gzip-retry test (both
  attempts compress in the same second, so it cannot see mtime) ->
  TestCompression::test_gzip_is_deterministic_across_time; e2e outbox
  untouched -> TestOutboxIsNotAQueue.
- mcp oauth: provider reuse across reconnects -> the manager restore test;
  the non-tty private predicate -> build_oauth_auth's fail-fast test; the
  manager disk-watch test -> the integration test, which gains its one
  unique assertion (an unchanged file returns False).
- startup watchdog: disarm-without-arm -> test_disarm_is_idempotent, whose
  second disarm runs in the same state and branch.
2026-09-29 15:58:55 +05:30
kshitijk4poor
b65972c1a5 test: give four vacuous tests an assertion that can fail
Each test passed with the behaviour it names removed (proven by mutating
the production owner on the old and new test):

- s6 hooks test_register_noop_on_host: the hook swallows the
  AssertionError into a printed warning, so reaching the manager never
  failed the test. Assert nothing was printed, as the sibling test does.
- live_system_guard bypass: signal 0 to our own PID passes the guard too.
  Assert os.kill is the unpatched builtin.
- retaindb queue_prefetch without a client: a missing guard fails only in
  background threads. Assert no prefetch thread was started.
- test_config golden migration parity: drop the `_config_version == 33`
  exact-match branch, which cannot run again now the version is 49; the
  subset check it fell through to is unchanged.

test_terminal_config_env_sync now drives gateway.run's
_bridge_terminal_config_to_env with every known terminal key instead of
parsing gateway/run.py's source for a dict literal. It asserts each key
reaches the same TERMINAL_* var as the CLI, which the key-set comparison
could not check, and removes the last inspect.getsource in tests/.
2026-09-29 15:58:55 +05:30
kshitijk4poor
1ad403f508 refactor(shared_metrics): remove reset_warning_latch_for_tests
A test-only export with one autouse fixture as its only caller. No test
asserts the warn-once line the latch guards, so resetting it protects
nothing.
2026-09-29 15:58:55 +05:30
kshitijk4poor
71fe5fccad refactor(model_switch): remove parse_model_flags, which only tests called
Every /model surface parses through parse_model_switch_args. The legacy
5-tuple wrapper had no production caller; its remaining references were two
tests of the tuple shape, an unused import, and two tui_gateway patch()
calls on a function tui_gateway no longer calls.

The en-dash --session normalization those tests covered moves into the
parse_model_switch_args keeper. test_model_switch_once_flags and
test_model_switch_context_offload are removed: the first re-tests the
private parser the keeper already drives, the second the one-line to_thread
wrapper that tests/gateway/test_model_command_context_offload.py proves off
the event loop through the real /model handler.
2026-09-29 15:58:55 +05:30
kshitijk4poor
9ee791eef1 test: drop tests that cannot fail or duplicate a same-file keeper
Each removed test either asserts nothing reachable or repeats another
test's exact body:

- auxiliary_client empty-base_url: body only writes config.yaml; a merge
  (890bbbda1f) restored the header of a test b3d4f67b20 had purged. The
  non-Anthropic-host test beside it drives the same default fallback.
- background_review no-session-db: _record_review_usage_to_parent swallows
  every exception and there is no DB to observe, so removing the guard
  still passes; the path has no observable effect to pin.
- code_execution Windows "would have failed" pair: every branch returns,
  skips or passes; they exercise CPython defaults, not Hermes code.
- cron test_should_use_direct_api_call_*: byte-identical copy of the test in
  tests/agent/test_cron_inline_api_call.py (0213b8b77b had to edit both).
- cron liveness no-multiplexer: identical body to test_no_lock_no_pids_is_false;
  its multiplexer patch is never reached.
- browser console/get_images "private URLs allowed": identical to the
  local-backend test in each file; both mock the predicate the name promises.
- hermes_state TestTitleUniqueness: the duplicate-title case is a strict
  subset of TestSessionTitleLineage::test_unrelated_session_still_conflicts;
  the NULL-title case never writes a title.
2026-09-29 15:58:55 +05:30
kshitijk4poor
bca2e3c5a4 docs(profiles): say the interactive uninstall menu still offers a full wipe
The stale-profile recovery paragraph claimed uninstall only runs in a
mode that keeps user data. Plain interactive `hermes uninstall` is let
through and its menu still offers Full uninstall of the default root,
behind its confirmation prompts. Say so, and note in the predicate why
--full is refused (it wipes without asking) while the menu is not.
2026-09-29 15:45:52 +05:30
kshitijk4poor
192e574efe docs(profiles): what still runs when the sticky profile was deleted 2026-09-29 15:45:52 +05:30
kshitijk4poor
fbb0eb35e1 fix(cli): missing sticky profile error names the recovery command
When active_profile names a deleted profile, the error still said
"Create it with: hermes profile create <name>", a command that exits with
the same error. Name the command that works instead:
`hermes profile use default`. An explicit -p keeps the create hint.
2026-09-29 15:45:52 +05:30
kshitijk4poor
167d3bb6f3 fix(cli): stale-profile recovery refuses uninstall --full and abbreviated --data
The recovery allowlist checked for the literal string "--data", but the
uninstall subparser accepts argparse abbreviations: `hermes uninstall --dat`
passed the guard and removed the default root's data. `--full` was allowed
outright, and in the default root it rmtree's every other profile under
profiles/ as well, a wider wipe than the --data mode the guard refuses.

Parse the uninstall argv with the real subparser (output silenced; the
handler reports errors itself) and allow it only when neither data nor full
resolves true. `uninstall --help` exits 0 during that probe and is let
through so the handler prints help once instead of help plus an error.
The predicate lives in a topical sibling (main_profile_recovery.py) rather
than growing the main.py facade.
2026-09-29 15:45:52 +05:30
Gille
a4748e9224 fix(cli): allow recovery from missing active profile 2026-09-29 15:45:52 +05:30
unsupportedpastels
536802c00e fix(debug): mask every secret fallback field per agent.redact policy, not only api_key
Reuses the repr-field secret-name policy (is_secret_field_name) so token, password, secret and credential-suffixed fields are masked by field in hermes dump, debug share and /debug. The regression test asserts on what actually reaches the paste service first.
2026-09-29 05:59:25 -04:00
unsupportedpastels
f907520593 fix(debug): mask fallback api_key by field, whatever its YAML shape
Text redaction of str(fallback_providers) missed api_key values that the
runtime still uses (resolve_entry_api_key str()s them): an unquoted numeric
key, a mapping, or a key containing "***" (treated as already masked).
Mask the field before serialising; keep the independent upload-boundary
pass and cover it with #126168's boundary test.

Co-authored-by: John Paul Soliva <soliva.johnpaul@icloud.com>
Co-authored-by: JoaoMarcos44 <joaomarcosdias444@gmail.com>
2026-09-29 05:59:25 -04:00
John Paul Soliva
cf94e3eea3 fix(redact): GCS and Azure SAS signatures count as URL credentials
The strict URL-credential pass that hermes dump and debug uploads use
redacted X-Amz-Signature but not X-Goog-Signature (GCS V4 signed URLs)
or sig (Azure SAS), so a fallback base_url signed that way still reached
the dump and the upload. The documented promise is signed-URL signatures
in general.
2026-09-29 05:59:25 -04:00
John Paul Soliva
139876e727 test(debug): URL credentials in a fallback base_url never reach hermes dump or an upload
Covers userinfo, ?key=, X-Amz-Signature= and #access_token= through the real config.yaml, hermes dump stdout, the share bundle and the /debug report. Docs (EN + zh-Hans) say the dump redacts credentials in fallback URLs.
2026-09-29 05:59:25 -04:00
JoaoMarcos44
205d5d78f1 fix(debug): strict URL-credential redaction for the system dump
A fallback_providers base_url can carry its credential in the URL (userinfo, ?key=, #access_token=, X-Amz-Signature=). The force pass leaves those to the log policy, which spares OAuth/magic-link URLs, so hermes dump printed them and debug share / gateway /debug uploaded them. The dump's fallback rendering and the upload copy of the dump now opt into redact_url_credentials.

The strict canonicalizer also turned x-amz-signature into x_amz_signature before the policy lookup, so strict mode never masked it; hyphenated policy names now match as stored.

Ported from #126168.
2026-09-29 05:59:25 -04:00
John Paul Soliva
e70fbbe3ec fix(debug): redact the system dump in debug share and gateway /debug
hermes dump prints config_overrides.fallback_providers as the raw list,
api_key values included, and the upload-time redaction of hermes debug
share and the gateway /debug report covered only the log snapshots, not
the dump text quoted at the top of every paste.

Force-redact the captured dump like the logs (--no-redact still opts
out), and have hermes dump itself redact fallback_providers so a pasted
dump never carries a key.
2026-09-29 05:59:25 -04:00
kshitijk4poor
ea7c6a1339 refactor(update): one explanation of the tail exemption, with the lock guard reworded
The lock-ancestry comment still claimed it was what stops a tail recursing;
the tail scripts now return before it, so it describes the processes an
update spawns instead. Also note why the identity reads sys.argv[0] rather
than the argv parameter.
2026-09-29 15:25:13 +05:30
kshitijk4poor
4e5cea136a test(update): a same-named script outside the checkout still repairs it
Pins the path half of the tail identity: only this checkout's own
hermes_cli/ scripts are exempt from the launch-time repair.
2026-09-29 15:25:13 +05:30
kshitijk4poor
6c7ebd1c01 fix(update): hermes update's own tail no longer starts a second tail
update_completion.py arms the pending marker, then imports hermes_cli.main,
which runs prepare_launch. While the updater's lock claim is live the
ancestor check returns early, but an unwritable claim or one past the
20-minute ceiling sends it into _finish_source_update: a full
source_completion tail runs inside the update and clears the marker before
the update's own build has finished.

Exempt both tail scripts through one identity check. The launch check
already resolves its root, so the comparison is exact on the process's
own argv[0], which the processes a tail spawns do not inherit.
2026-09-29 15:25:13 +05:30
Gille
de48024d75 fix(update): prevent recursive source completion
(cherry picked from commit 481e68381ebb9bbc36dd7b99876b6fd8b833584a)
2026-09-29 15:25:13 +05:30
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
be475657ea docs(update): recover a partial clone stuck on the git pack-objects crash (#124272)
Manual marker one-liners for installs whose own updater cannot fetch the fix,
and the missing-object backfill for checkouts that were already turned into
full clones by hand.
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
kshitijk4poor
282ad01cb5 fix(update): mark unmarked packs instead of the inert promisor retry (#124272)
git 2.53+ `index-pack --promisor` hands every object outside the promisor
packs to pack-objects, which BUG()s (should_include_obj) on the missing
objects those objects lead to. A partial clone with unmarked packs therefore
keeps crashing its fetches.

The retry added for this ran `git -c remote.origin.promisor= fetch`. That
override does nothing: git registers promisor remotes additively, so the
repo's own `true` still wins (GIT_TRACE on 2.50 and 2.55 shows
`index-pack --promisor` and `filter tree:0` under the override). The retry
only passed when the crashed fetch had already stored the pack; a retry that
has to transfer again crashed again.

- On the crash, give every pack without a `.promisor` marker one, then retry
  the same fetch once. The clone stays partial, no refetch, no config change.
  If git still crashes, the updater points at the docs section.
- The two fetches that turn a full clone partial now mark the clone's old
  packs afterwards: the depth-1 `tree:0` unshallow in
  `fetch_full_commit_graph`, and `worktree_ops._deepen_shallow_repo`
  (`--filter=blob:none`). git writes the partial-clone config before it
  fetches, so this also runs when the fetch fails. Without it, a converted
  depth-1 install crashed every later fetch on git 2.55 (3 of 3 rounds in a
  local repro).
2026-09-29 15:24:15 +05:30
Q
70b9cdafd4 fix(update): recognize the git 2.55 partial-clone pack-objects crash
is_partial_clone_pack_objects_crash() required "index-pack failed" next to the
pack-objects assertion, but git 2.55 builds dropped that string: Homebrew git
2.55.0 (macOS) only reports "fatal: fetch-pack: invalid index-pack output", so
the recognizer always returned False there, fetch_with_partial_clone_recovery()
never retried with the promisor machinery disabled, and `hermes update` exited 1
on the crash (#125138 reports the same bare-assertion failure on 2.55.0.windows.5).

Fingerprint on the assertion text — the only version-stable part of the failure
— and accept the 2.55 wrapper as a terminator. The POSIX/Windows shapes and the
"fingerprint without a terminator" rejection behave as before.

(cherry picked from commit 74103b15ceb3eacb2346fca669c3a3dc81457201)
2026-09-29 15:24:15 +05:30
kshitijk4poor
af26acab73 fix(deps): gate the runtime takeover on genuinely historical updater frames
The salvaged matcher accepted any cmd_update/_cmd_update_impl frame in
hermes_cli.update_cmd/main, so the CURRENT updater's entrypoints (which
carry pre_update_version on opts, not as a local) would also hand off and
exit the process mid-`hermes update`. Extract
_old_updater.in_historical_update() sharing _historical_context's exact
frame matcher (module, name, and pre_update_version in co_varnames) so the
shim and the context detector cannot drift apart.

- tools/lazy_deps: use the shared predicate; accept the constraints/
  dry_run kwargs historical updaters pass so they reach the handoff
  instead of dying on TypeError; drop the vague message advice.
- tests: collapse 29 parametrize cases to the real invariants and add the
  missing one — the frame walk is now exercised through a nested helper
  (a getframe(1)-only implementation fails), and a current-updater-shaped
  frame (no pre_update_version local) is asserted NOT to hand off.
2026-09-29 15:08:07 +05:30
funky-xamarin
5e6165562b fix(deps): refuse runtime lazy installer update takeovers 2026-09-29 15:08:07 +05:30
kshitijk4poor
7728574ad4 test(cli): fold foreign-venv wrapper case into the migration test
The standalone ownership test duplicated the parametrized boot migration
(same wrapper, weaker check), was the only test in tests/hermes_cli calling
the private _publish_conveniences, and asserted a mtime-derived "changed"
flag immediately after writing the file, which is flaky on filesystems with
coarse timestamps.

Keep its one unique invariant, another checkout's venv/bin/hermes is never
adopted, by pointing the migration test's existing foreign hermes-agent
wrapper at another checkout's venv. It still also mentions this install in a
comment, so both rejection cases stay covered through public expose_cli().
2026-09-29 14:36:55 +05:30
Gille
91986da496 fix(cli): adopt legacy venv hermes wrapper
(cherry picked from commit ccdbc32d93358c1d0c8219ab3e9441a8a49fdbee)
2026-09-29 14:36:55 +05:30
Gille
ea114c3e98 fix(install): preserve apostrophe paths in Windows runtime launch 2026-09-29 13:19:27 +05:30