From 0fc162e8cd2ce0dcc0af5916b7a2d1cfb819f461 Mon Sep 17 00:00:00 2001 From: Godopu Date: Sun, 25 Oct 2026 19:31:10 +0000 Subject: [PATCH] fix(lib.sh): line breaks in a name become '-' like Python's; load_state_json strict (Issue #71) - 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 --- .agents/skills/lib.sh | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/.agents/skills/lib.sh b/.agents/skills/lib.sh index 7217137..e873ff3 100644 --- a/.agents/skills/lib.sh +++ b/.agents/skills/lib.sh @@ -39,7 +39,8 @@ AGENT_SESSIONS_YAML="${AGENT_SESSIONS_YAML:-$WORKSPACE_ROOT/.mam/agent-sessions. # shasum, sha1sum, openssl or python3 to make it, this fails (stderr, nonzero) # rather than return a name herdr never registered. A name that is not ASCII # is lowercased and replaced by python3 (tr/sed differ from str.lower() there), -# and fails the same way without it. +# and fails the same way without it. Line breaks become '-' before sed (which +# works line by line) and before $(...) could drop a trailing one. _sanitize_herdr_agent_name() { local n="$1" if [ -z "$n" ]; then @@ -48,7 +49,7 @@ _sanitize_herdr_agent_name() { fi local s="" if [ -z "$(printf '%s' "$n" | LC_ALL=C tr -d '\000-\177')" ]; then - s=$(printf '%s' "$n" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9_-]/-/g') + s=$(printf '%s' "$n" | tr '\n' '-' | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9_-]/-/g') elif command -v python3 >/dev/null 2>&1; then s=$(python3 -c 'import re,sys; sys.stdout.write(re.sub(r"[^a-z0-9_-]", "-", sys.argv[1].lower()))' "$n") || s="" fi @@ -1590,10 +1591,14 @@ herdr() { # --------------------------------------------------------------------------- # NOTE: Retained as small inline PYEOF block (39 lines) per Plan Rev.2 (Job 98393a97). # Small blocks have minimal overhead and high local cohesion; extraction into lib_py is reserved for large blocks (>200 lines). +# `load_state_json strict` (Issue #71) fails (stderr, exit 1) when the YAML or +# DB cannot be read, instead of printing {} - for a caller that acts on a +# session being absent. load_state_json() { - env_python "$AGENT_SESSIONS_YAML" <<'PYEOF' + env_python "$AGENT_SESSIONS_YAML" "MAM_STATE_READ=${1:-}" <<'PYEOF' import os, sys, sqlite3, json, yaml yaml_path = os.environ['YAML_PATH'] +strict = os.environ.get('MAM_STATE_READ') == 'strict' db_path = os.path.splitext(yaml_path)[0] + '.db' d = {} db_sessions = [] @@ -1605,21 +1610,25 @@ try: row = conn.execute('SELECT data FROM state WHERE id=1').fetchone() if row: d = json.loads(row[0]) - except sqlite3.OperationalError: - pass + except sqlite3.OperationalError as e: + if strict and 'no such table' not in str(e): + raise try: cursor = conn.execute('SELECT data FROM sessions') for r in cursor.fetchall(): db_sessions.append(json.loads(r[0])) d['herdr_sessions'] = db_sessions - except sqlite3.OperationalError: - pass + except sqlite3.OperationalError as e: + if strict and 'no such table' not in str(e): + raise conn.close() elif os.path.exists(yaml_path): with open(yaml_path) as f: d = yaml.safe_load(f) or {} -except Exception: - pass +except Exception as e: + if strict: + print('ERROR: cannot read the registry %s: %s' % (yaml_path, e), file=sys.stderr) + sys.exit(1) # Clean lone surrogates to prevent UnicodeEncodeError on stdout print def clean_surrogates(obj): if isinstance(obj, str):