You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
install (v0.10.8, Windows): Hermes pre_llm_hook_install refuses config when a plain scalar contains > / | anywhere mid-value; the same value double-quoted installs fine — bisected to two persona strings #1924
Filed from a debugged end-user install; supersets/continues #1631 and #1656 (both closed as completed, but the underlying refusal is still reachable in v0.10.8 — this is a new failing construct, not a re-report of the four fixed in v0.10.6).
Summary
codebase-memory-mcp install on Windows consistently fails for the Hermes agent with op=pre_llm_hook_install against %LOCALAPPDATA%\hermes\config.yaml. Because agent-config failures abort activation, the whole install exits 1 and user-PATH registration is skipped even though 11 other detected agents configure fine.
Deterministic, config-content-dependent: key-by-key and persona-by-persona bisection reduced the trigger to how a scalar is serialized, then to a single character class. A plain (unquoted) scalar that contains > anywhere after the value start is refused; the identical string emitted double-quoted is accepted. Plain | mid-value behaves the same. Plain * mid-value is accepted (consistent with the * fix in v0.10.4).
This matches the indicator-scoping rule you already adopted — "quotes and anchors are indicators only where a value begins, the same rule the * fix used in v0.10.4" (v0.10.6 notes) — but apparently that scoping was not extended to > and |.
The local config is repaired and install/update now completes end-to-end (exit 0, idempotent). Details and a confirmed minimal A/B/C repro below.
error: agent_config agent=Hermes op=pre_llm_hook_install path=C:\Users\<user>\AppData\Local\hermes/config.yaml (target: regular file, 4952 bytes)
error: one or more agent configurations failed; the published/current executable was kept, and PATH/index cleanup was not attempted
error: activation stopped after one or more agent configuration or cleanup operations failed; the published/current executable was kept, and configuration changes that completed may remain. Please restart your coding-agent sessions after resolving the errors above.
...
error: installation failed (exit code 1)
On the very first (virgin) run the mcp_servers: write into the same file succeeded (present on disk thereafter); from then on only pre_llm_hook_install refused — consistent with mcp_install no-op'ing on its existing entry while the hook upsert re-validated the file. (In #1656 on 0.10.5 both ops failed; on 0.10.8 only the hook op fails for our file.)
PR #1629's (target: regular file, N bytes) descriptor is what made this tractable — it correctly signaled a structural refusal on a writable regular file, not permissions or a missing file. Thank you. What remains missing is which structural rule refused (the single--1 collapse noted in #1629 is exactly what turned this into a multi-hour key-level bisect).
Each variant: write only this config to %LOCALAPPDATA%\hermes\config.yaml, run codebase-memory-mcp install -y --force --dir=...:
# A — PASS (exit 0): plain scalar, mid-value `*`, no `>` → v0.10.4 `*` fix confirmed workingagent:
personalities:
probe: hewwo *nuzzles your code* nya# B — FAIL (exit 1, op=pre_llm_hook_install): plain scalar, mid-value `>`agent:
personalities:
probe: kaomoji face >w< here# C — PASS (exit 0): the identical string double-quotedagent:
personalities:
probe: "kaomoji face >w< here"# D — FAIL (exit 1, op=pre_llm_hook_install): plain scalar, mid-value `|`agent:
personalities:
probe: pipe char | mid value
Battery results (each variant alone, same machine, same binary):
variant
mid-value char
style
result
A
*
plain
PASS (exit 0)
B
>
plain
FAIL — op=pre_llm_hook_install
C
>
double-quoted
PASS (exit 0)
D
|
plain
FAIL — op=pre_llm_hook_install
Root-cause isolation (how B was reached from the real config)
Key-level bisect of the 4.8 KB Hermes config (all runs with the file LF-normalized, no BOM, original data otherwise byte-equal after a PyYAML round-trip):
step
config subset
result
1
all 22 original keys + canonical mcp_servers/hooks
FAIL
2
keys 0–11 (model…memory, incl. toolsets: [- all] block seq at col 0)
Serialization detail (PyYAML allow_unicode=True, width=100000): the refusing variant emitted kawaii/uwu as plain scalars with > after value start. PyYAML itself accepts this — it is valid YAML — indicator characters (and quotes, and *) are only significant at value start, so a mid-scalar > is ordinary content. The refusal is cbm's scanner being stricter than the YAML spec here, which is presumably deliberate fail-closed behavior by design; the problem is it fails closed + silent + install-aborting rather than reporting the construct.
failure is content-dependent; in passing: Set-Content -Encoding UTF8 repros (ours and #1656's 26-byte one) carry a UTF-8 BOM + CRLF, which are extra variables — worth re-running #1656's repro with printf-written LF content
UTF-8 BOM
original has none (xxd); BOM-free fixtures both pass and fail depending on > placement
not the cause (BOM may be its own refusal — untested in isolation on 0.10.8)
CRLF line endings
130-line CRLF→LF conversion, size 4952→4822
still fails — not the cause
cross-account DACL grant (the acl-grants-cross-account-mutation family install.ps1 documents for its staging dir)
explicit non-inherited BUILTIN\Administrators:(F) found on config.yaml; removed via RemoveAccessRuleSpecific+Set-Acl → DACL then identical in shape to agents that pass (only inherited SYSTEM/Administrators/)
still fails — not the cause (for the hook op)
directory/file ownership
hermes dir owned by BUILTIN\Administrators (created by an elevated Hermes install); took ownership of dir + children to the invoking user
The refusal reason is collapsed into a single exit path (PR fix(cli): say what the agent-config target IS when a write is refused #1629 acknowledges: "the editors should carry their own refusal reason out"). The (target: regular file, N bytes) descriptor was essential and made the bisect possible — a reason string ("unsupported indicator in scalar" + offset) would collapse the remaining space to minutes.
Re-serialized config.yaml with PyYAML using a representer that forces double-quoted style for any string containing >, | (and defensively backtick), LF endings, single-line scalars. All original key/value data preserved (verified parsed-data equal to the original). Hermes reads it fine; install now exits 0 and the canonical hook lands:
Repeated install -y --force runs are idempotent (exit 0, hook: recognized, no re-mutation chatter).
Suggested fix / regression tests
Scanner: extend the v0.10.4/v0.10.6 indicator-scoping rule ("indicators only where a value begins") to > and | — a mid-scalar >/| must not flip the scanner into block-scalar/refuse state. (Plain * mid-value is already correctly accepted per the battery above.)
0.10.6: fixes four YAML constructs — both reporters' configs install end-to-end
0.10.8: configs containing a plain mid-value > (or |) still refuse; only pre_llm_hook_install fails when mcp_servers was already written
Note on install: Hermes agent_config fails on Windows (content-independent), aborting PATH registration #1656's "content-independent" repro: its 26-byte fixture was written with Set-Content -Encoding UTF8 (PowerShell 5.1), which emits CRLF + UTF-8 BOM; my identical fixture also failed, while a printf-written LF/no-BOM fixture with the same data passed. The true trigger remained content-dependent (>), hidden until the key-level bisect. The mixed-separator lead did not hold: passing a forward-slash LOCALAPPDATA changed nothing (the path is normalized internally and the target resolves regardless).
Happy to test a candidate build against the full corpus (the original 4.8 KB config + the A/B battery above). Config fragments above are minimized; identifying details redacted (username/hostname → <user>/<hostname>).
Filed from a debugged end-user install; supersets/continues #1631 and #1656 (both closed as completed, but the underlying refusal is still reachable in v0.10.8 — this is a new failing construct, not a re-report of the four fixed in v0.10.6).
Summary
codebase-memory-mcp installon Windows consistently fails for the Hermes agent withop=pre_llm_hook_installagainst%LOCALAPPDATA%\hermes\config.yaml. Because agent-config failures abort activation, the whole install exits1and user-PATH registration is skipped even though 11 other detected agents configure fine.>anywhere after the value start is refused; the identical string emitted double-quoted is accepted. Plain|mid-value behaves the same. Plain*mid-value is accepted (consistent with the*fix in v0.10.4).*fix used in v0.10.4" (v0.10.6 notes) — but apparently that scoping was not extended to>and|.install/updatenow completes end-to-end (exit 0, idempotent). Details and a confirmed minimal A/B/C repro below.Environment
296,140,288bytes, daemon cohort buildb4b403b1d7c4def3785f148b93f345ce8427858f4f5489ce28580c4387a336a6install.ps1fromraw.githubusercontent.com/DeusData/codebase-memory-mcp/main/install.ps1)yaml.safe_loadround-trip OK), owned by the invoking user, no BOMC:\Users\<user>\..., hostname<hostname>Symptom
On the very first (virgin) run the
mcp_servers:write into the same file succeeded (present on disk thereafter); from then on onlypre_llm_hook_installrefused — consistent with mcp_install no-op'ing on its existing entry while the hook upsert re-validated the file. (In #1656 on 0.10.5 both ops failed; on 0.10.8 only the hook op fails for our file.)PR #1629's
(target: regular file, N bytes)descriptor is what made this tractable — it correctly signaled a structural refusal on a writable regular file, not permissions or a missing file. Thank you. What remains missing is which structural rule refused (the single--1collapse noted in #1629 is exactly what turned this into a multi-hour key-level bisect).Confirmed minimal A/B/C reproduction (v0.10.8, Windows)
Each variant: write only this config to
%LOCALAPPDATA%\hermes\config.yaml, runcodebase-memory-mcp install -y --force --dir=...:Battery results (each variant alone, same machine, same binary):
*>op=pre_llm_hook_install>|op=pre_llm_hook_installRoot-cause isolation (how B was reached from the real config)
Key-level bisect of the 4.8 KB Hermes config (all runs with the file LF-normalized, no BOM, original data otherwise byte-equal after a PyYAML round-trip):
mcp_servers/hooksmodel…memory, incl.toolsets: [- all]block seq at col 0)session_reset,skills,agent,platform_toolsets, …)skillsaloneagentaloneagentw/ only scalar fields (max_turns,verbose,reasoning_effort) + one persona at a timehelpful✅ ·kawaii❌ ·catgirl✅ ·pirate✅ (single-quoted''doubling) ·shakespeare✅ ·surfer✅ (apostrophe in double quotes) ·noir✅ ·uwu❌ ·hype✅ (emoji +LET'S)kawaii,uwurefuse>mid-value ((>ω<),>w<); no other persona contains>anywhereSerialization detail (PyYAML
allow_unicode=True, width=100000): the refusing variant emittedkawaii/uwuas plain scalars with>after value start. PyYAML itself accepts this — it is valid YAML — indicator characters (and quotes, and*) are only significant at value start, so a mid-scalar>is ordinary content. The refusal is cbm's scanner being stricter than the YAML spec here, which is presumably deliberate fail-closed behavior by design; the problem is it fails closed + silent + install-aborting rather than reporting the construct.Ruled out during triage (each with evidence)
model: glm-4.7LF/no-BOM → PASSSet-Content -Encoding UTF8repros (ours and #1656's 26-byte one) carry a UTF-8 BOM + CRLF, which are extra variables — worth re-running #1656's repro withprintf-written LF contentxxd); BOM-free fixtures both pass and fail depending on>placementacl-grants-cross-account-mutationfamilyinstall.ps1documents for its staging dir)BUILTIN\Administrators:(F)found onconfig.yaml; removed viaRemoveAccessRuleSpecific+Set-Acl→ DACL then identical in shape to agents that pass (only inherited SYSTEM/Administrators/)hermesdir owned byBUILTIN\Administrators(created by an elevated Hermes install); took ownership of dir + children to the invoking userLOCALAPPDATA=C:/Users/<user>/AppData/Local(forward slashes)regular file, N bytes) → not the causesafe_loadOK; 130 lines; no anchors, no multi-doc, no tabs, no control bytes (iconvstrict pass)pre_llm_callentrypre_llm_hook_install, notpre_llm_hook_foreign; grep confirms nohooks:/pre_llm_callin the file pre-writeImpact
(target: regular file, N bytes)descriptor was essential and made the bisect possible — a reason string ("unsupported indicator in scalar" + offset) would collapse the remaining space to minutes.Workaround applied locally (works, data-preserving)
Re-serialized
config.yamlwith PyYAML using a representer that forces double-quoted style for any string containing>,|(and defensively backtick), LF endings, single-line scalars. All original key/value data preserved (verified parsed-data equal to the original). Hermes reads it fine;installnow exits 0 and the canonical hook lands:Repeated
install -y --forceruns are idempotent (exit 0,hook:recognized, no re-mutation chatter).Suggested fix / regression tests
>and|— a mid-scalar>/|must not flip the scanner into block-scalar/refuse state. (Plain*mid-value is already correctly accepted per the battery above.)>mid-value (refused today), plain|mid-value (refused today), double-quoted>(passes today — keep passing), single-quoted''doubling (passes today),*mid-value (passes today — guards the 0.10.4 fix), plus a persona-corpus fixture resembling a real Hermes default config (agent.personalitieswith kaomoji/emote strings).Triage timeline (in case it is useful)
mcp_install+pre_llm_hook_installboth fail (Hermes: config.yaml mcp_install and pre_llm_hook_install refused on Linux and Windows (regressed again in v0.10.4) #1631); 0.10.3: mcp fixed; 0.10.4: mcp regressed; 0.10.5: both fail, reported content-independent (install: Hermes agent_config fails on Windows (content-independent), aborting PATH registration #1656)>(or|) still refuse; onlypre_llm_hook_installfails whenmcp_serverswas already writtenSet-Content -Encoding UTF8(PowerShell 5.1), which emits CRLF + UTF-8 BOM; my identical fixture also failed, while aprintf-written LF/no-BOM fixture with the same data passed. The true trigger remained content-dependent (>), hidden until the key-level bisect. The mixed-separator lead did not hold: passing a forward-slashLOCALAPPDATAchanged nothing (the path is normalized internally and the target resolves regardless).Happy to test a candidate build against the full corpus (the original 4.8 KB config + the A/B battery above). Config fragments above are minimized; identifying details redacted (username/hostname →
<user>/<hostname>).