@ssheleg/agent-sync 1.18.5 → 1.18.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,40 @@
1
+ ## v1.18.6 — the guard keeps its own header's promise, and /clear gets its own identity
2
+
3
+ Wave-2 hardening from the 2026-08-29 family audit (ASY-07, ASY-08, ASY-06, ASY-01).
4
+ Same shape as v1.18.5: text that claimed more than the code did.
5
+
6
+ - **ASY-07 — a machine without python3 had no guard, and said nothing.** `guard.sh`'s
7
+ header promises that every internal failure exits 2, because any other outcome is
8
+ non-blocking in Claude Code — and the python3-absent path broke it: both parser
9
+ substitutions failed under `2>/dev/null`, `path` came back empty, `is_commit`
10
+ defaulted to 0 and the hook exited 0. Fail OPEN, exactly where the header says it
11
+ must not, and nothing upstream compensates — hooks.json's `if` filter is best-effort
12
+ by doctrine, so the parser IS the guard. Both substitutions now report through
13
+ `parser_or_die`, which exits 2 and names the remedy (install python3 / fix PATH, or
14
+ remove `.claude/agent-sync.json` to switch coordination off).
15
+ `check_guard_fails_closed_without_python3` drives the real hook through a shim PATH
16
+ holding bash and cat but no interpreter — watched failing against the shipped guard
17
+ (`the guard exited 0`) — and the self-test plants the fail-open back
18
+ (`an absent interpreter fails open`).
19
+ - **ASY-08 — a post-/clear session inherited the ended run's identity.** The
20
+ SessionStart matcher read `startup|resume`, and `/clear` ends the run while keeping
21
+ the CLI process — so the stamp keyed by that process survived into the next session:
22
+ "two sessions, one identity", the case SKILL.md itself names, and `release` then
23
+ takes a lease the caller never had. The matcher now carries `clear`; `compact` stays
24
+ excluded deliberately, because a compaction continues the SAME session.
25
+ `hooks_session_test.py` gained the case (matcher parts asserted both directions, and
26
+ the hook re-stamps on a clear payload) — watched failing against the shipped matcher.
27
+ - **ASY-06 — the guard's internal-failure refusal named no next step.** A refusal with
28
+ no remedy is how an operator learns to switch a hook off. The message now says:
29
+ diagnose with `python3 <script> check`, or acquire a lease first.
30
+ - **ASY-01 — `scripts/__pycache__` regenerated forever.** Hooks now export
31
+ `PYTHONDONTWRITEBYTECODE=1` in `_lib.sh` (sourced by all four), and the test files
32
+ that import `agent_sync.py` set `sys.dont_write_bytecode` — the shipped `scripts/`
33
+ directory stays source, not build products.
34
+ - ASY-04 (this test class missing from CI) was already closed in v1.18.5:
35
+ `validate.yml` runs `hooks_session_test.py` since commit `5446990`. Verified, not
36
+ re-done. ASY-02 (command rename) closed by operator decision 2026-08-30: names stay.
37
+
1
38
  ## v1.18.5 — a pipe reaches the guard, and the installers stop deleting blind
2
39
 
3
40
  Two enforcement holes, each of the same shape: a mechanism whose own text claimed more
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ssheleg/agent-sync",
3
- "version": "1.18.5",
3
+ "version": "1.18.6",
4
4
  "description": "Let concurrent coding agents share one project without colliding — leases with TTL, race-free id reservation, a run journal and a generated board, over a pluggable knowledge cloud.",
5
5
  "bin": {
6
6
  "agent-sync": "bin/agent-sync.js"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "agent-sync",
3
3
  "displayName": "Agent Sync",
4
- "version": "1.18.5",
4
+ "version": "1.18.6",
5
5
  "description": "Coordination layer for multi-agent repositories — leases with TTL, race-free ID reservation, a run journal, a cross-repo signal feed and a generated board, over a pluggable knowledge cloud.",
6
6
  "author": {
7
7
  "name": "ssheleg",
@@ -1,6 +1,11 @@
1
1
  #!/usr/bin/env bash
2
2
  # Shared helpers for the agent-sync hooks. Sourced, never executed.
3
3
 
4
+ # Every hook runs python3 against the shipped scripts/ directory, which people read
5
+ # as source, not as a build product. Bytecode caching buys nothing at this call rate
6
+ # and left scripts/__pycache__ regenerating forever (ASY-01).
7
+ export PYTHONDONTWRITEBYTECODE=1
8
+
4
9
  # Run a command under a time limit, portably.
5
10
  #
6
11
  # `timeout` is GNU coreutils and is NOT on a stock macOS. Calling it directly made
@@ -8,6 +8,17 @@ S="$AGENT_SYNC_PY"
8
8
  agent_sync_configured || exit 0
9
9
  input=$(cat)
10
10
 
11
+ # The parser IS the guard: hooks.json's `if` filter is best-effort and fails open by
12
+ # doctrine, so nothing upstream compensates for a parser that cannot run. A python3
13
+ # missing from PATH used to leave `path` empty, default `is_commit` to 0 and exit 0 —
14
+ # the one machine state that disables the parser silently disabled the guard (ASY-07).
15
+ parser_or_die() {
16
+ local rc="$1"
17
+ [ "$rc" -eq 0 ] && return 0
18
+ echo "agent-sync: the guard could not run its parser (python3 exit $rc — likely missing from PATH). Failing closed. Install python3 or fix PATH and retry, or remove .claude/agent-sync.json to switch coordination off here." >&2
19
+ exit 2
20
+ }
21
+
11
22
  path=$(python3 -c '
12
23
  import json,sys
13
24
  try:
@@ -17,6 +28,7 @@ except Exception:
17
28
  ti=d.get("tool_input") or {}
18
29
  print(ti.get("file_path") or ti.get("path") or ti.get("notebook_path") or "")
19
30
  ' <<<"$input" 2>/dev/null)
31
+ parser_or_die $?
20
32
 
21
33
  # git commit: check every staged path instead of a single file argument.
22
34
  if [ -z "$path" ]; then
@@ -30,7 +42,7 @@ if [ -z "$path" ]; then
30
42
  #
31
43
  # Tokenised in python rather than globbed in shell: `git log --grep=commit` must not match, and
32
44
  # `git -c user.name=x -C dir commit` must.
33
- read -r is_commit repo <<<"$(python3 -c '
45
+ parsed=$(python3 -c '
34
46
  import json, shlex, sys
35
47
  try:
36
48
  d = json.load(sys.stdin)
@@ -71,7 +83,9 @@ for seg in cmd.replace("&&", "\n").replace("||", "\n").replace("|&", "\n").repla
71
83
  repo = r
72
84
  break
73
85
  print(is_commit, repo)
74
- ' <<<"$input" 2>/dev/null)"
86
+ ' <<<"$input" 2>/dev/null)
87
+ parser_or_die $?
88
+ read -r is_commit repo <<<"$parsed"
75
89
  [ -n "${is_commit:-}" ] || is_commit=0
76
90
  [ -n "${repo:-}" ] || repo="."
77
91
  [ -d "$repo" ] || repo="${CLAUDE_PROJECT_DIR:-$PWD}"
@@ -100,6 +114,6 @@ else
100
114
  echo "$out" >&2
101
115
  exit 2
102
116
  fi
103
- echo "agent-sync guard failed to run ($code): $out" >&2
117
+ echo "agent-sync guard failed to run ($code): $out — diagnose with \`python3 $S check\`, or acquire a lease first: \`python3 $S acquire <TASK-ID>\`" >&2
104
118
  exit 2
105
119
  fi
@@ -3,7 +3,7 @@
3
3
  "hooks": {
4
4
  "SessionStart": [
5
5
  {
6
- "matcher": "startup|resume",
6
+ "matcher": "startup|resume|clear",
7
7
  "hooks": [
8
8
  {
9
9
  "type": "command",
@@ -4,7 +4,7 @@ description: "Use when several coding agents work one repository at the same tim
4
4
  compatibility: "Requires the task-pipeline skill for its stages (npx sshlg-skills install). Needs python3 3.9+ (stdlib only, HTTP included - nothing to pip install) and bash for the hooks. The knowledge backend is configured per project; with none configured it degrades to git-file leases. Enforcement hooks are Claude Code only - on other agents the same checks run as a self-check."
5
5
  license: MIT
6
6
  metadata:
7
- version: "1.18.5"
7
+ version: "1.18.6"
8
8
  author: ssheleg
9
9
  ---
10
10
 
@@ -33,7 +33,7 @@ from datetime import datetime, timezone
33
33
  from pathlib import Path
34
34
  from typing import Any
35
35
 
36
- VERSION = "1.18.5"
36
+ VERSION = "1.18.6"
37
37
 
38
38
  CONFIG_PATH = Path(".claude/agent-sync.json")
39
39
  ENV_FILE = Path(".env.agent-sync")