Ports the system-atlas agent skill: one data.mjs file renders both an
interactive isometric HTML map (progressive-disclosure chapters, moving
data packets, question tracking by Q-ID) and a generated text twin
(SYSTEM.md) for the repo.
Why: architecture discussions that outgrow a single diagram — the skill
encodes hard-earned rules (max 3 structures per chapter, shapes+labels,
docs-policy ask before committing). Upstream 410 stars since Aug 20,
VoltAgent-listed; complements architecture-diagram (static SVG) with an
explorable, stateful artifact.
- optional-skills/creative/system-atlas/: SKILL.md (89 lines),
references/ (design language, process lessons), assets/ (build.mjs,
template.html, data.example.mjs) vendored verbatim; LICENSE.txt (MIT,
Harshyt Goel)
- Live smoke: node --check both .mjs OK; node build.mjs produced
atlas.html (41.5 KB) + SYSTEM.md from the example data, zero deps
- docs: own catalog row + generated page + sidebar entry only
Port of https://github.com/yanliudesign/mono-color-skill (MIT, 2.9k
stars in 3 weeks). Generates original one-ink or controlled two-ink
editorial print images (risograph/duotone/monochrome poster aesthetic)
driven by machine-readable design-system catalogs: substrate/ink
palettes, composition geometry, typography roles, rhythm, controlled
imperfections — all vendored verbatim as the source of truth.
Upstream 34 KB SKILL.md restructured into a hub (143-line core + three
references). Image generation rebound to image_generate; hardcoded
'~/Desktop/Claude skills' path replaced with a neutral output dir.
Upstream examples/ are all-rights-reserved (ASSET-LICENSE.md) and are
NOT vendored — MIT text + catalogs only, with a NOTICE. optional-skills/
placement.
Ports the pr-lens agent skill: represent a diff or subsystem as one
graph.json document and render it as animated SVG diagrams via the MIT
npx CLI (@coldtea/pr-lens-cli), with optional opt-in publishing to a
shareable canvas link.
Why: PR review and architecture explanation keep producing hand-drawn
Mermaid; this gives validated, animated, drill-down diagrams with a
deterministic document format. Upstream created Aug 20, 1.1k stars in
3 weeks, GitHub App + Action + CLI + skill.
- optional-skills/software-development/pr-lens/: SKILL.md (145 lines),
references/ (config, graph document format, valid example) vendored
near-verbatim, LICENSE.txt (MIT, Coldtea AI)
- gh --attach caveat handled: installed gh 2.97 lacks the flag; skill
documents honest fallbacks (gist, canvas link, local path)
- Live smoke: validate + render of the vendored example graph passed
(4 SVGs + manifest produced)
- docs: own catalog row + generated page + sidebar entry only
The twozero TouchDesigner integration now lives in one installable unit:
plugin-catalog/touchdesigner.yaml points at NousResearch/hermes-plugin-touchdesigner
(portable Agent Plugins v1: mcp.json registers the twozero Streamable HTTP hub, skills/
carries touchdesigner-mcp). `hermes plugins install touchdesigner` + `hermes plugins
enable td` replaces the optional skill whose setup.sh hand-wrote an mcp_servers block.
The manifest name is `td` because Hermes names portable MCP tools
mcp__agent_plugin_<name>_<hash>__<server>__<tool>; twozero's longest tool under a
`touchdesigner` namespace is 71 chars, past the 64-char provider function-name cap.
optional-skills/creative/touchdesigner-mcp and its generated docs (bundled, optional,
zh-Hans) are removed; catalog tables, sidebar and kanban-video-orchestrator references
are updated to point at the plugin. Supersedes #68607 (MCP-catalog-only approach).
Install with hermes skills install official/autonomous-ai-agents/dynamic-workflow.
Orchestration-campaign guidance is niche enough not to sit in every default prompt index.
tt-a1i/archify (MIT, 57k stars) turns a typed JSON spec into validated,
self-contained interactive HTML diagrams (architecture / workflow /
sequence / dataflow / lifecycle) with a 9-check validate/deliver receipt,
Mermaid import and PNG/SVG/WebM export. The upstream repo already ships a
complete skill directory (archify/), so this follows the impeccable
pattern: a catalog stub whose metadata.hermes.upstream pointer makes
`hermes skills install official/creative/archify` pull the live tree
through OptionalSkillSource._fetch_from_upstream. Nothing executable is
vendored and the copy can never go stale.
Docs: generated page, one catalog row, one sidebar line.
Credit: tt-a1i (upstream author). Supersedes the vendored-references
port in #106442.
Two zero-install research skills plus routing guidance, ported as ideas
(not code) from the Agent Reach skill's per-platform backend routing.
reddit-reading (skills/social-media): subreddit listings, site/subreddit
search, threads with comments, user pages. Live-verified from a datacentre
IP: www .json, api.reddit.com, old.reddit, r.jina.ai and the browser tool
all return 403 / an empty shell / a humanity check; the Atom .rss endpoints
are the only anonymous path and are throttled to ~1 request/min/IP. The
script waits out the x-ratelimit-reset window once and retries, and
switches to the OAuth API (scores, nested comments, ~100 req/min) when
REDDIT_CLIENT_ID/SECRET are present. `doctor` reports the active backend.
rss-feeds (skills/research): RSS 2.0 / RSS 1.0 / Atom / JSON Feed parsing
with UTC-normalised dates, --since/--limit, and feed discovery behind a
page URL (<link rel=alternate>, then well-known paths). Stdlib only; the
optional blogwatcher skill remains the stateful many-feed reader and now
points at this one for one-off reads.
grounded-citations gains a "Multi-Platform Sweeps" section routing
"what are people saying about X" tasks across web, Reddit, feeds, video,
code and X with per-platform attribution and coverage-gap reporting;
competitor-news-monitor references the new sources.
Tests: two invariant tests per skill (format normalisation + discovery;
anonymous 429 handling + OAuth routing/flattening), no network. The
reddit test caught a real bug: the feed footer's "/u/author" leaked into
post bodies.
* refactor(skills): shipped-set slim — 15 skills to optional, github six-way merge, pdf absorbs OCR+nano-pdf, channel-gated teams pipeline
Maintainer-directed shipped-skills curation (skills index 1,900 -> ~1,400
tok/call on desktop; every session pays the index, so this is a per-call
diet on all installs):
- optional-skills moves (installable via skills hub, history preserved):
creative comfyui/ascii-art/excalidraw/pretext/sketch/touchdesigner-mcp;
ALL of mlops (huggingface-hub, llama-cpp, serving-llms-vllm,
weights-and-biases, evaluating-llms-harness — subcategory structure
kept); research-paper-writing (55 supporting files, 17.3K-tok load);
openhue; blogwatcher (first taught the cronjob monitor-field watch
pattern + web_extract instead of pre-cron manual workflows)
- DELETED session-librarian (Aug-12 'inspired by Perplexity Computer'
port, never maintainer-intended; session_search covers discovery)
- github: six skills (auth, issues, pr-workflow, issue-to-pr,
code-review, repo-management) merged into ONE software-development/
github skill — routing body + complete per-workflow references;
benbarclay authorship credited; codebase-inspection rides along;
discipline pins from test_github_issue_to_pr_skill.py preserved
against the reference body in the new test_github_skill.py
- pdf absorbs ocr-and-documents + nano-pdf as references/ + scripts
(extract_pymupdf, extract_marker converted to the argparse house
standard its contract test enforces)
- NEW session_platforms frontmatter gate (metadata.hermes): hides a
skill from the index on gateway channels it is not for; fail-open on
unknown platform; teams-meeting-pipeline gated to [teams, cron]
- blocked-page-recovery: research -> new web category; trigger-first
description ('Use when a fetch fails: 403/429, paywall, WAF, bot
wall.') so the model actually reaches for it on blocked fetches
- docs regenerated via generate-skill-docs.py (195 pages); related_skills
swept repo-wide; tests: 1672 passed (2 openclaw failures pre-existing
on clean main, Windows-local)
* chore: ignore .skills_prompt_snapshot.json (local index cache, accidentally committed)
Bundled by #83063 into the always-shipped set, but it serves only
multi-agent kanban campaigns — too niche for every install's skills
index (~20 tok/call for all users). Moved to the optional catalog
(installable via /skills search + hub, official source) and renamed so
the trigger is legible at a glance: 'merge-reconciler' read like a
generic git helper; 'agent-merge-conflict-arbiter' says who it is for.
- skills/autonomous-ai-agents/merge-reconciler -> optional-skills/autonomous-ai-agents/agent-merge-conflict-arbiter
- frontmatter name + description updated (description within the 60-char hardline its own contract test enforces)
- contract test moved/renamed, 9/9 green
- kanban docs (en + zh-Hans) repointed; zero merge-reconciler refs remain
platforms field added, description under the 60-char hardline, dangling
premium-webapp-ui related_skills ref dropped (user-profile skill, not in
the repo tree). Docs page + catalog row regenerated to match.
hermes skills install impeccable (and the docs-page install button) now
installs the impeccable frontend-design skill as an official optional-skills
entry. The local optional-skills/creative/impeccable/ dir is a catalog STUB:
its frontmatter declares metadata.hermes.upstream (repo + path), and
OptionalSkillSource.fetch() pulls the real 163-file bundle live from
pbakaus/impeccable:.hermes/skills/impeccable — the Hermes-native bundle
upstream maintains and verifies. Nothing vendored, never stale.
New mechanism (generic, not impeccable-specific):
- OptionalSkillSource._upstream_pointer(): parses/validates the upstream
pointer (owner/name repo, clean relative path, traversal rejected).
- _fetch_from_upstream(): delegates to GitHubSource.fetch(), relabels the
bundle official/<rel> at trust 'trusted' (curated endorsement, but
third-party content — dangerous scan verdicts still block).
- The live-repo fallback path redirects stubs the same way, so stale local
checkouts behave identically.
Three real gaps this surfaced, all fixed:
- GitHubSource.fetch() only downloaded SKILL.md plus paths linked from a
canonical support dir (references/, scripts/, ...). Impeccable keeps its
playbooks under reference/ (singular) and links scripts only from
reference files, so fetch shipped 1 of 163 files. fetch() now downloads
the full skill directory via the git tree (same approach as the
optional-skills live fetch), still rejecting symlinks/hidden/unsafe paths
and still failing on a missing SKILL.md-linked references/ path.
- The five env_exfil_* scanner patterns flagged loopback requests as
critical exfiltration: impeccable's live mode polls
http://localhost:PORT/status?token=TOKEN and scored two CRITICALs.
Scheme-anchored loopback exemption added; evil.com/?u=localhost decoys
still fire (10-case regex matrix in tests).
- unified_search() truncated to limit before ranking, so official catalog
entries got crowded out by skills.sh mirrors and bare-name installs
stalled on an ambiguity table. Results now stable-sort by trust rank
before the cut, and _resolve_short_name prefers a sole official exact
match over community mirrors.
Also fixes pre-existing test pollution: TestInstallPathSafety's fixture
monkeypatched the PEP 562 dynamic SKILLS_DIR, permanently shadowing dynamic
resolution and breaking the served_repo E2E tests in any combined run
(reproducible on main).
Validation: live E2E do_install("impeccable") against real GitHub —
resolves to official/creative/impeccable, verdict SAFE, 163 files on disk,
skill loads, /impeccable slash command registers. 128/128 targeted tests;
full-dir fetch test sabotage-verified. Docs: optional-skills catalog row,
generated skill page, sidebar.
The bundled plan skill's auto-generated slash command fell off the capped
Telegram/Discord command menus for most installs (skills are the only tier
trimmed at the platform caps, alphabetically — 'plan' sat past the cutoff at
index 57 of 82 bundled skills). Converting it to a first-class CommandDef
gives it a guaranteed core-tier menu slot on every platform.
- agent/plan_prompt.py: build_plan_prompt() — plan-mode rules + authoring
craft distilled from the retired skill; prompt-injection pattern like
/learn and /init (no engine, no model-tool footprint, cache-safe).
- CLI: _handle_plan_command mixin handler (pending-input injection).
- Gateway: /plan branch rewrites event.text and falls through (role
alternation preserved).
- TUI: command.dispatch branch ('plan' was already in
_PENDING_INPUT_COMMANDS).
- Removed skills/software-development/plan/ + docs pages (EN + zh-Hans),
catalog rows, sidebar entry, related_skills references.
- PROTECTED_BUILTIN_SKILLS is now empty (mechanism kept); dependent
curator/usage tests moved to monkeypatched sentinels.
Salvages #67292 by @webtecnica (credit: first /plan command submission,
issue #67264); reworked from inline planning prompt to the prompt-injection
pattern with workspace-saved plans. Closes#67264, closes#36821 (empty
/plan infers task from conversation context).
Reverts the plan-interrogation rename from #97831 per maintainer decision —
the skill keeps its original grill-me name. The content upgrade (design-tree
frontier-rounds interview mechanic from mattpocock/skills' grilling) stays.
Docs page, catalog row, and sidebar entry renamed back.
Ports the MIT-licensed 'to-questionnaire' skill from mattpocock/skills as
decision-questionnaire (optional-skills/productivity). Interviews the user
about the send only (recipient + needed outcomes), then drafts a
most-important-first discovery questionnaire with answer stubs to
decision-questionnaire-<slug>.md. Includes staging tests for frontmatter
standards, template sections, and de-upstreaming.
Ports the MIT-licensed 'wizard' skill from mattpocock/skills as
setup-wizard-generator (optional-skills/devops). Generates an interactive
bash wizard that walks a human through manual procedures: opens dashboard
URLs, captures values (hidden entry for secrets), writes .env / GitHub
secrets idempotently, and confirms each stage. Vendors upstream's
template.sh library verbatim (bash -n verified) plus a staging test suite
covering frontmatter, template integrity, and de-upstreaming.
Renames the optional grill-me skill to the more descriptive plan-interrogation
and upgrades it with the design-tree frontier-rounds mechanic from
mattpocock/skills' MIT-licensed 'grilling' skill: batch all currently-askable
questions per round in dependency order, agent finds facts itself, decisions
stay with the user. Docs page, catalog row, and sidebar entry renamed.
Per the 'when in doubt, optional' rule — plan-interview is an
on-request capability, not a weekly daily-driver for most users.
Install via: hermes skills install official/software-development/grill-me
Per the 'when in doubt, optional' rule — site publishing is an
on-request capability, not a weekly daily-driver for most users.
Joins cloudflare-temporary-deploy/page-agent under
optional-skills/web-development (existing category, existing
DESCRIPTION.md kept; the new bundled category dir is dropped).
Install via: hermes skills install official/web-development/publish-site
Replaces the stale MCP-first AgentMail skill with CLI-first guidance:
self-signup + OTP verification, inbox/message/thread/label/attachment
flows, webhook and WebSocket delivery references, and MCP as an
alternative path. Declares AGENTMAIL_API_KEY (optional) so a stored key
reaches the sandboxed terminal while self-signup stays viable without
one.
Salvaged from PR #60811 — kept in optional-skills/ per the March 2026
decision that third-party-API-key skills are not bundled.
npx hyperframes preview starts a long-lived next-server that keeps
chrome-headless-shell render workers resident. On GPU-less hosts (WSL,
containers, CI) each idle worker falls back to software WebGL (swiftshader)
and busy-spins a CPU core; a preview left open stacks these up until the
host is wedged. The skill never said preview was long-lived, so leaking
was the default outcome.
Add a Cleanup section + pitfall to SKILL.md and a Runaway CPU
troubleshooting entry with diagnose/fix/avoid steps.
Adds scripts/har_capture_cdp.py for browsers reached over CDP -- cloud
backends (Browserbase, Browser-Use, Firecrawl), Camofox-with-CDP, and any
/browser connect endpoint. record_har_path only works on a locally-owned
Playwright context, so the CDP capturer attaches via connect_over_cdp() and
assembles the HAR from page request/response events instead, leaving the
attached browser open (it doesn't own it).
- SKILL.md: pathway->capturer routing table, CDP prerequisites, pitfalls for
wrong-capturer/empty-HAR, headless-UA weakness, and no-close-on-attach
- Validated live: attached to an external CDP Chrome, drove DuckDuckGo
autocomplete, derived the /ac/ endpoint, replayed it browserless
- tests: assert CDP capturer attaches (not launches) and that the skill
documents every browser backend
Record a site's XHR into a HAR with Playwright, derive its private JSON API,
and call it directly over plain HTTP instead of browser-controlling the page
every time. Credit: trick by Jared Longster, popularized by Dax (thdxr).
- scripts/har_capture.py: Playwright HAR recorder with scripted --action steps
and embedded response bodies
- scripts/har_to_client.py: distills the HAR to endpoints (method/path template
/params/body/response) plus User-Agent+cookie+auth replay hints
- Validated live: derived + replayed the Algolia HN-search POST API and the
Wikipedia rest.php search-title GET, both browserless
- tests exercise the real derivation logic on a synthetic HAR fixture
optional-skills placement: heavy Playwright dependency, niche use case.
Two Windows agent-loop friction fixes:
1. tools/mcp_tool.py (#56536): shutil.which(cmd, path=env_path) reads
executable extensions from the PARENT process PATHEXT, not the MCP
subprocess env — a stdio MCP config supplying both PATH and PATHEXT
could fail to resolve a command its own env can locate, and startup
then got a bare command name. On Windows, when the first which() call
misses and the config env carries PATHEXT (any key casing), retry the
resolution with the config's PATHEXT temporarily applied.
2. skills/ + optional-skills/ (#50606): 42 SKILL.md files that declare
platforms: [.., windows] used python3 in their command examples.
python3 does not exist on native Windows (the toolchain probe in the
system prompt reports python3=missing), so every copy-pasted example
burned a failed agent turn before self-correction. Replaced the
command word python3 -> python (python3-config / python3.x version
strings untouched). python is the spelling that exists in every
Hermes-managed environment (Windows native, uv-managed venvs on all
three OSes); agents on POSIX hosts additionally see the probed
toolchain line and adapt either way.
The upstream ahujasid/blender-mcp and ahujasid/ableton-mcp GitHub repos
were hijacked on 2026-08-08: the maintainer (@sidahuj) publicly reported
his account was compromised and ownership stripped, and both repos now
redirect to an attacker-controlled org (MCPBlender, created the same
day, pushing new commits since).
Although our catalog pinned blender-mcp==1.6.4 from PyPI (pre-compromise,
sha256 verified unchanged), the server is only half the bridge: the
manifest's post-install instructions and the optional skill directed
users to download addon.py — arbitrary Python executed inside Blender —
from the now-compromised GitHub repo (the raw URL currently 404s, and
the addon ships in no PyPI artifact). There is no trustworthy source
for the addon half, so the entry cannot be installed safely end-to-end.
Removing the catalog entry and skill entirely until the maintainer
confirms account recovery; re-adding is a follow-up PR once upstream
is verified clean.
- optional-mcps/blender/: removed
- optional-skills/creative/blender-mcp/: removed
- docs: catalog rows, sidebar entry, skill pages (en + zh-Hans) removed
- cross-references in unreal-mcp and kanban-video-orchestrator cleaned
test_authoring_standards.py::test_description_hardline red on main since
461c493972 landed with a 383-char description. The trimmed detail is all
preserved in the SKILL.md body (When-to-use, decision tree, search_files
comparison). Unbreaks every open PR's slice 4.
New tests/skills/test_authoring_standards.py parametrizes every bundled +
optional SKILL.md (1148 checks) against the mechanically-verifiable subset
of the hardline standards:
- required frontmatter fields (name/description/version/author/license/
platforms) + tags
- frontmatter name == directory name
- description <= 60 chars, ends with period, no marketing words
- related_skills resolve in-repo
- no machine-local paths
- <= 100k chars
Grandfather dict for legacy debt ships EMPTY — all pre-existing violations
fixed in this PR:
- 13 frontmatter names canonicalized to their directory names (the install
identifier); all related_skills references updated (comfyui -> stable-
diffusion). Fixes the class behind PR #42788's report; also fixes
here.now's invalid dot-name.
- optional-skills/devops/cli -> inference-sh-cli (dir was the generic
'cli'; fm name was right) incl. docs pages (en + zh-Hans), catalog row,
sidebar entry.
- pytorch-fsdp: 157k generated 'Quick Reference' dump moved to
references/common-patterns.md; SKILL.md 159k -> 2.5k with a pointer.
- research-paper-writing: 31.7k Phase 5 drafting section moved to
references/phase5-paper-drafting.md; SKILL.md 103k -> 71k.
Docs regenerated with scope discipline.
- description 210 -> 57 chars; author credits Ben Barclay (benbarclay) first
- optional-skills/creative/ (marketing vertical, narrowest audience of
the batch)
- dropped phantom 'image-generation-workflow' ref; visuals via the
image_generate tool
- honest handoff language: platforms without connectors end at approved
drafts marked handed-off, never claimed as published
- tests (10) incl. phantom-ref and honest-handoff guards
- docs regen scoped: per-skill page + one catalog row + one sidebar line
Findings from scanning skills/ + optional-skills/ with NVIDIA
SkillEvaluator's deterministic Tier 1 checks (PII/secrets, unicode
smuggling, script lint):
- pixel-art, pokemon-player: remove hardcoded /home/teknium/ personal
paths (use ~ / portable phrasing); pokemon-player no longer claims
machine-specific state as fact
- kanban-video-orchestrator: replace <path> angle-bracket token in
frontmatter credits (flagged as XML-in-frontmatter prompt injection)
- comfyui, hermes-agent, unsloth, 1password, actual-setup: rephrase
placeholder secrets so they no longer pattern-match real credentials
(your-* placeholder convention, comment markers, {env:...} form)
- docker-management, pytorch-lightning: drop user:pass@ from example
connection strings (env/secret-manager guidance instead)
- evm: break up Keccak round constant that Luhn-validates as a credit
card number (digit-group underscores, value unchanged)
All targeted skills now pass pii+unicode+lint 3/3 except unsloth, which
retains scanner false positives only (Colab notebook IDs read as Bitcoin
addresses; an email inside a quoted upstream system prompt).
Fleet audit showed these task skills are commonly needed across users;
shipping bundled per Teknium's direction. Docs and tests follow the
bundled paths.
Per the 'when in doubt, optional' rule — niche prediction-market data
skill that sees no regular use; belongs alongside stocks in the finance
optional category rather than the default bundle.
Install via: hermes skills install official/finance/polymarket
- optional-skills/devops/actual-setup: field-tested setup skill contributed
by shl0ms, updated for the first-class 'actual' provider (the original
targeted a custom-provider config that now collides with the built-in name)
- docs: providers.md section + tables, environment-variables.md, quickstart.md
- tests/skills: frontmatter + first-class-provider conformance checks
Exact parity with utils.atomic_replace: its target fsync is wrapped in
try/except OSError. A failed fsync after a successful copy must not
surface the already-completed write as an error (Windows can raise on
fsync of a read-only handle).
- agent_import.dump_yaml_file now calls utils.atomic_yaml_write instead
of hand-rolling safe_dump + atomic_write_text — same temp+fsync+atomic
rename and symlink preservation, plus mode/owner preservation a
0600-secured config.yaml needs
- openclaw script: the EXDEV/EBUSY copy fallback gains copystat + target
fsync so the docstring's 'mirrors utils.atomic_replace' durability
claim is true on cross-device deployments
- trim load_yaml_file's docstring to the behavior contract
The inlined temp-file + os.replace in openclaw_to_hermes.dump_yaml_file
replaced a symlinked config.yaml with a regular file, silently detaching
managed deployments that symlink ~/.hermes/config.yaml into a dotfiles repo or
profile package. The bare path.write_text it replaced followed the link, and
utils.atomic_replace -- which the hermes_cli twin reaches through
atomic_write_text -- resolves the link for exactly this reason (#16743).
Mirror that here: resolve the symlink before creating the temp file so the
rename lands on the real file, and fall back to copyfile on EXDEV/EBUSY now
that the target can live on another device. Covered by a regression test that
fails when the resolution is removed.
Also guard the permission-denied test for Windows: os.geteuid does not exist
there and chmod-based denial is unreliable, so skip on non-POSIX.
agent_import.py carries a private load_yaml_file/dump_yaml_file pair that
returned {} for an absent file AND for a present file it could not read or
parse. Three importers -- import_permission_allowlist, import_permission_denylist
and import_mcp_servers -- read config.yaml through it, merge one section into
the result, and write the whole mapping straight back. So a YAML syntax error,
a permission problem or a broken mount meant the importer replaced every
setting the user had with only the one to three keys it merged, and still
reported the item as "imported". The write was a bare path.write_text(), so an
interrupted import truncated the file instead.
Distinguish the two cases at the read. Absent, or present but empty, still
yields {} so first-time creation works. Present but unreadable, unparseable, or
not a mapping raises ConfigReadError; the three sites funnel through a new
load_target_config() that records the refusal as a per-item error and leaves the
file byte-identical. Dry-run refuses too, rather than previewing an "imported"
that would destroy the config. dump_yaml_file now writes through
utils.atomic_write_text, which the module already imports and uses for the
memory store.
This is the invariant hermes_cli/config.py already enforces for its own writers
via require_readable_config_before_write / atomic_config_write, whose docstring
names this exact root cause and calls itself "the single chokepoint every
config-update path should use". agent_import.py has its own helper pair and so
was never covered; it was the last config.yaml writer without the guard.
The identical helper pair lives in openclaw_to_hermes.py, the script this module
was ported from, where twelve config.yaml read-modify-write sites share the same
defect; fixed there too. Its refusal is recorded at the run_if_selected dispatch
point, which flips the existing _config_apply_blocked flag so the remaining
config-mutating options short-circuit instead of each rediscovering the same
unreadable file. The atomic write is inlined with tempfile + os.replace because
that script runs standalone with only the stdlib on its path.
Follow-ups from sweeper review of #43763:
- tests/docker/test_tini_compat_shim.py asserts the dispatcher
ENTRYPOINT (with /init delegation check) instead of a bare /init
- tests/docker/test_smoke.py gains a docker run --init regression
for the non-PID-1 fallback (#38349)
- website/docs/user-guide/docker.md and the s6 supervision skill
document the dispatcher and its wrapped-runtime fallback
memories/MEMORY.md is the "§"-delimited store written by MemoryStore, not a
markdown document. parse_existing_memory_entries() fell back to
extract_markdown_entries() -- the *source* parser for CLAUDE.md / AGENTS.md --
whenever the destination held no delimiter, which is exactly the case for a
single-entry store or one that was hand-edited or shell-appended. That
extractor skips fenced code blocks, skips table rows, splits a block into one
entry per bullet and reflows paragraphs. The shredded result was then written
straight back over the user's store and reported as "Imported", with no backup
to recover from.
Parse the destination the way MemoryStore._parse_entries does: split on
ENTRY_DELIMITER only, so a store with no delimiter is one intact entry.
extract_markdown_entries() is unchanged and still used on the sources, where
it is correct.
Also restore the safety net the port dropped. The openclaw migration script
this module was ported from calls maybe_backup(destination) before rewriting a
memory store; the port did not. Snapshot the store to <name>.bak.<unix_ts>
(same naming as MemoryStore._backup_drifted_file), refuse to rewrite when the
snapshot fails, and write via temp file + atomic rename so an interrupted
import cannot leave a truncated store and a symlinked MEMORY.md stays a
symlink.
The identical fallback lives in openclaw_to_hermes.py, where it is reached
from migrate_memory() (memories/MEMORY.md and memories/USER.md) and
migrate_daily_memory(); fixed there too.
AST-driven pass over every Path.read_text()/write_text() without an
explicit encoding= across non-test code: 71 sites in 34 files
(skills_hub, hermes_cli/main+profiles+service_manager+container_boot,
mem0/hindsight/honcho plugins, achievements dashboard, release/CI
scripts, productivity+comfyui skill helpers, agent/*). Verified zero
positional-encoding collisions before insertion; per-file compile()
check after.
Adds a check-windows-footguns rule flagging bare single-line
read_text/write_text (multi-line forms stay covered by the AST guard
test from #38985). Together with the salvaged contributor commits this
retires the ~169-site bare file-I/O class (#37423's long tail).