Skip to content

fix(probe): rename snapshot_token JSON wire key to snapshot_pin - #59

Merged
Adolanium merged 3 commits into
Adolanium:mainfrom
enochmessi2:fix/rename-snapshot-token-wire-key
Nov 6, 2026
Merged

Adolanium merged 3 commits into
Adolanium:mainfrom
enochmessi2:fix/rename-snapshot-token-wire-key

Conversation

@enochmessi2

@enochmessi2 enochmessi2 commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Rename the snapshot_token JSON wire key to snapshot_pin

Fixes the resetwatch probe/plugin RPC being dead on every first page for any user whose gateway runs stdout redaction (all Hermes installs since 2026-11-05).

Root cause: the key name, not the value

Upstream hermes-agent commit fb637f5bc8 (2026-11-05) added _JSON_ANY_KEY_FIELD_RE (agent/redact.py:551): a redaction rule that masks any identifier-shaped JSON field whose name contains a credential word — here, token. It reached Hermes via merge ca0c921999 (2026-11-05 21:14).

The gateway redacts all shell.exec child stdout (gateway/session_shell.py:75, force=True). So the probe's stdout line:

{"snapshot_token": "872c6085abdbfe1e689e7a2b0348db3a", "snapshots": [...]}

left the gateway as:

{"snapshot_token": "872c60...db3a", "snapshots": [...]}

plugin.js validates the token with its own /^[a-f0-9]{32}$/ check, which the masked value fails — every first page, 100% of the time. The panel throws probe returned invalid snapshot pin and renders nothing. This is not an intermittent failure; it is deterministic on any redacting gateway.

A/B proof (same data, same redaction, only the key name differs)

Run through the real gateway entrypoint redact_sensitive_text(force=True, redact_url_credentials=True) — the exact call at gateway/session_shell.py:75:

input:                    {"snapshot_token": "aaa…32hex…", "snapshots": []}
output with snapshot_token → {"snapshot_token": "aaaaaa…aaaa", …}   ← masked → /32-hex/ rejects → dead panel
output with snapshot_pin   → {"snapshot_pin": "aaa…32hex verbatim…"} ← unmasked → panel works, 2 snapshots returned

Renaming the wire key sidesteps the credential-word heuristic without weakening redaction for anyone — the token value is still a secret-shaped string, but the gateway's rule keys on field names, so the name no longer trips it.

What changed / what deliberately did not

Changed — the JSON wire key only, 6 lines across plugin.js + probe.py (both trees; catalog/ regenerated with scripts/build_catalog.py):

  • plugin.js emit object + 3 read sites (snapshot_token → snapshot_pin)
  • probe.py 2 _emit_json sites

Deliberately unchanged:

  • CLI flag --snapshot-token= — argv is not redacted (only stdout JSON is), so the flag is safe and its name stays.
  • Token value (still 32-hex), pin filename (probe_page.<32hex>.json), and the /^[a-f0-9]{32}$/ validation.
  • Error string probe returned invalid snapshot pin.

Tests that assert on the key updated in lockstep: test_probe_pagination.cjs, test_probe_slicing.py, test_profile_routing.cjs, test_quick_usage.cjs, plus the one prose mention in README.md (wire-protocol docs). README still documents --snapshot-token=TOKEN.

Verification

node test_probe_pagination.cjs  → 14/14 pass
node test_profile_routing.cjs   → 27 pass, 1 env-conditional skip (pre-existing, unrelated)
node test_quick_usage.cjs       → 44/44 pass
python test_probe_slicing.py    → Ran 6 tests ... OK
python test_catalog_policy.py   → Ran 2 tests ... OK
python scripts/build_catalog.py --check → "Catalog package verified"

The same fix is already shipped and verified locally on both deployed plugin copies (~/.hermes/plugins/hermes-resetwatch/ and ~/.hermes/desktop-plugins/hermes-resetwatch/, byte-identical), where it restored the panel. This PR is the same rename upstream so every other Hermes user on current main stops hitting the dead panel.

Note for reviewers

scripts/build_catalog.py --check (CI) passes: the generated catalog/desktop/ tree is in the same commit as the root sources. No other occurrences of snapshot_token remain in the repo (repo-wide grep is clean).

enochmessi2 and others added 3 commits November 6, 2026 12:16
The gateway redacts shell.exec child stdout with a rule that masks any identifier-shaped JSON field whose NAME contains a credential word (agent/redact.py _JSON_ANY_KEY_FIELD_RE). The probe's stdout JSON key snapshot_token therefore left the gateway pre-masked, and plugin.js's own /^[a-f0-9]{32}$/ check rejected every first page: the panel was dead 100% of the time.

Rename the JSON wire key only. The 32-hex token VALUE, the pin file name probe_page.<32hex>.json, the /^[a-f0-9]{32}$/ validation, and the --snapshot-token= CLI argv flag (argv is not redacted, only stdout is) are all unchanged. Tests asserting on the key are renamed to match, and the catalog package is regenerated.
Desktop and a remote gateway's probe can be on different versions for a while. A gateway that does not redact still sends an older probe's pin intact, so read snapshot_pin first and fall back to snapshot_token. A masked legacy value still fails the 32-hex check. Tests cover both.
Adds Adolanium#59 to the contributor thanks in the README.
@Adolanium
Adolanium merged commit 29382b0 into Adolanium:main Nov 6, 2026
1 check passed
@Adolanium

Copy link
Copy Markdown
Owner

Merged, thank you @enochmessi2, and welcome!

Tracing this to the key name instead of the value made the fix obvious. Your A/B through the real redact_sensitive_text(force=True, ...) call settled it. I reran it against current hermes-agent main and got the same result: snapshot_token comes out as 872c60...db3a, and snapshot_pin passes through intact. Since NousResearch/hermes-agent@fb637f5bc8, every first page on a redacting gateway failed the 32-hex check. Renaming the wire key is the right fix, and redaction stays as strong as before for everyone else.

I added one follow-up commit on top of yours. plugin.js now reads snapshot_pin first and falls back to snapshot_token. Desktop and a remote gateway's probe can run different versions for a while, and a newer Desktop shouldn't break against an older probe on a gateway that doesn't redact. A legacy pin that was masked still fails the check. Two new tests in test_probe_pagination.cjs cover both cases, and the legacy one fails without the fallback. I also added you to the thank-you line in the README.

All the workflow steps pass in CI and locally. I didn't run the panel against a redacting gateway in Desktop myself, so I'm relying on your check of the two deployed copies for that.

Catalog installs are pinned to 0.2.27, so they get this once a new release is pinned upstream. Until then, you can copy plugin.js and probe.py from main. Your local copies don't need the fallback, because the probe and plugin there are already on the same version.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants