A DB without its sessions table (or without both tables) was read as an
empty registry in strict mode, so stop closed a registered session
row-less and exited 0. Strict mode now fails on any OperationalError;
the non-strict reader is unchanged.
Refs #71
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- parity and create with real line breaks in the name;
- stop with malformed YAML (no DB) or a corrupt DB refuses (exit 1);
- test_layout: the shim heredoc anchor follows the { ... } > "$tmp_file"
form (the rule it checks is unchanged);
- S11: base re-pinned to 0fc162e, which changes the shim's sanitize body.
Refs #71
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
load_state_json printed {} on a YAML/DB read error, so a registered
session was closed row-less (exit 0) and its stop workflow skipped.
stop now reads the registry strictly and exits 1 before any herdr call.
Pins of the shifted stop_session.sh lines updated.
Refs #71
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The ASCII branch of _sanitize_herdr_agent_name translates line breaks
before sed (line by line) and $(...) (drops a trailing one) can lose
them: "newline-creator-claude\n" was started as newline-creator-claude
while its attach_command named newline-creator-claude-.
- `load_state_json strict` exits 1 (stderr) when the YAML or DB cannot be
read instead of printing {}; without the argument nothing changes.
Refs #71
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- create with a non-ASCII name: the attach target is the name herdr has;
bash/Python parity for non-ASCII and option-like names; no python3 is
an error for a non-ASCII name.
- row-less stop without --herdr-session uses HERDR_SESSION_NAME; an
unreadable row is not taken for no row.
- a retry (entry point, or any write) repairs a YAML a failed migration
left in format 1.
- tier3: the "cannot infer agent" case registers its row, as tier1 does.
- S11: base re-pinned to 5cd72d2, which changes the shim's sanitize body.
Refs #71
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The writer commits the migrated DB before it replaces the YAML. If that
replacement failed, the DB said format 2 and the next run did nothing.
When the DB is current the writer now also reads the YAML's format and
rewrites a YAML that is older or missing.
Refs #71
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With no --herdr-session the server is chosen as create_session.sh chose
it (HERDR_SESSION_NAME, else the workspace's), not always derived from
the workspace. The row-less path runs only when the lookup says the row
is missing (exit 7); any other lookup failure stops with exit 1 as
before. Pins of the shifted stop_session.sh lines updated.
Refs #71
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tr/sed lowercase differently from str.lower() outside ASCII, so a name
like "İstanbul-creator-claude" was started as x--stanbul-... while the
row's attach_command named i-stanbul-.... A non-ASCII name is now
lowercased and replaced by python3 (fail closed without it), and the
name is read with printf, not echo ("-n" no longer vanishes).
Refs #71
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- attach_command is `herdr --session <server> agent attach <herdr name>`
(`default` written too), built by lib_py.agents.sanitize.attach_command;
create, resume and reconcile all use it. kill_command and start_command
are no longer written, and status --json drops start_command.
- The two raw kill-session hints (create exit 43, id_order's orphan) print a
stop-skill command with --herdr-session instead.
- stop_session.sh closes a session that has no registry row, herdr only and
without --agent, when herdr runs it inside this workspace; another
workspace's session is refused (exit 5), a missing one is exit 1, one that
survives kill-session is exit 6. The registry is not changed.
- Docs: only the SKILL.md lines these changes made wrong; the skill block
pins follow. Tests that asserted the removed fields keep their intent
(issue50: create's own row; issue21: no control-root path in
attach_command; tier2: the new form and the absent fields). S11 pins the
shim to 4243da5, which changed it on purpose.
Refs #71
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- lib_py/registry_format.py: a pure, idempotent migrate() drops
kill_command/start_command and rebuilds attach_command on every row
(stopped/terminated included) and sets the top-level registry_format key.
- atomic_dump_yaml migrates an older registry under its lock, before the
caller's mutation, and then rewrites the YAML as well as the DB (the YAML
write is otherwise skipped when only fields change). load_state_json stays
read-only. New registries are written with the current version.
- Entry point for #72: `python -m lib_py.registry_format --yaml <path>` runs
the migration through the writer and prints
`registry-format: <from> -> <to>, rows changed: <n>`.
Refs #71
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
StallWatch converts --target once and hands the name to HerdrSource, so
agent get/read/prompt and agent wait cannot call herdr by different names.
Refs #71
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- One _sanitize_herdr_agent_name: the shim gets lib.sh's definition through
`declare -f` instead of a second copy. Without shasum/sha1sum/openssl it
hashes with python3 (same result as lib_py.agents.sanitize); with none of
them it fails instead of truncating or appending 00000000.
- Every caller stops on that failure: new-session before any pane is made,
the lookups report failure, and has-session takes the name lib.sh computed
(the cwd-relative import and its lower()[:32] fallback are gone).
- `herdr agent attach <name>` through the shim resolves the target like
get/read/prompt; options after the target are kept.
Refs #71
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>