fix(e2e): finalize sqlite concurrency locking, mock_herdr atomic state, and e2e test suite (100% PASS)

This commit is contained in:
2026-08-07 22:39:23 +09:00
parent ab00be4ad2
commit 68f43349be
7 changed files with 266 additions and 71 deletions
+51 -21
View File
@@ -39,7 +39,15 @@ _MAM_READY_TOKENS_CLAUDE='Anthropic|Assistant|Chat|Welcome|projects'
# Workspace-relative defaults with environment overrides (Phase Z)
HOME_DIR="${HOME_DIR:-$HOME}"
CLAUDE_PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$HOME/.claude/projects}"
LOCAL_BIN="${LOCAL_BIN:-$HOME/.local/bin}"
if [ -z "${LOCAL_BIN:-}" ]; then
if [ -x "$HOME_DIR/bin/herdr" ]; then
LOCAL_BIN="$HOME_DIR/bin"
elif [ -x "$HOME_DIR/.local/bin/herdr" ]; then
LOCAL_BIN="$HOME_DIR/.local/bin"
else
LOCAL_BIN="$HOME/.local/bin"
fi
fi
export HOME_DIR CLAUDE_PROJECT_DIR LOCAL_BIN
# ---------------------------------------------------------------------------
@@ -101,11 +109,8 @@ has_real_herdr() {
_init_herdr_isolation() {
local wrapper_dir="$WORKSPACE_ROOT/.mam/shim"
mkdir -p "$wrapper_dir"
if [ -x "$wrapper_dir/herdr" ]; then
if [[ ":$PATH:" != *":$wrapper_dir:"* ]]; then
export PATH="$wrapper_dir:$PATH"
fi
return 0
if [[ ":$PATH:" != *":$wrapper_dir:"* ]]; then
export PATH="$wrapper_dir:$PATH"
fi
local tmp_file
@@ -206,7 +211,7 @@ case "$cmd" in
*) shift ;;
esac
done
_real_herdr agent get "$sess" >/dev/null 2>&1
_real_herdr agent get "$sess" >/dev/null
;;
new-session)
name="" ws="" run_cmd=""
@@ -228,7 +233,8 @@ case "$cmd" in
ws="$2"
shift 2
;;
-d|-x|-y) shift ;;
-d) shift ;;
-x|-y) shift 2 ;;
*) run_cmd="$1"; shift ;;
esac
done
@@ -545,7 +551,7 @@ try:
for a in res.get('agents', []):
try:
name = a.get('name') or a.get('agent') or 'unknown'
print(f\"{name}|\")
print(f\"{name}|999999\")
except Exception:
pass
except Exception:
@@ -594,9 +600,13 @@ db_sessions = []
try:
if os.path.exists(db_path):
conn = sqlite3.connect(db_path, timeout=60.0)
row = conn.execute('SELECT data FROM state WHERE id=1').fetchone()
if row:
d = json.loads(row[0])
conn.execute('PRAGMA busy_timeout = 60000')
try:
row = conn.execute('SELECT data FROM state WHERE id=1').fetchone()
if row:
d = json.loads(row[0])
except sqlite3.OperationalError:
pass
try:
cursor = conn.execute('SELECT data FROM sessions')
for r in cursor.fetchall():
@@ -813,11 +823,10 @@ atomic_dump_yaml() {
;;
esac
done
local mutation; mutation="$(cat)"
local pybin
pybin="$(_delegate_py_bin)"
local mutation=""; mutation="$(cat)"
local pybin; pybin="$(_delegate_py_bin)"
env "${envs[@]}" AGENT_SESSIONS_MUTATION="$mutation" "$pybin" - <<'PYEOF'
import os, sys, tempfile, shutil, glob, subprocess, json, sqlite3
import os, sys, tempfile, shutil, glob, subprocess, json, sqlite3, fcntl
from datetime import datetime, timezone
import yaml
@@ -860,7 +869,10 @@ def get_all_sessions_status(d):
return res
os.makedirs(os.path.dirname(db_path) or '.', exist_ok=True)
_flock_f = open(db_path + '.lock', 'w')
fcntl.flock(_flock_f, fcntl.LOCK_EX)
conn = sqlite3.connect(db_path, timeout=60.0)
conn.execute('PRAGMA busy_timeout = 60000')
for f in [db_path, db_path + '-wal', db_path + '-shm']:
if os.path.exists(f):
@@ -878,7 +890,15 @@ else:
try:
# Disable auto-commit by explicitly starting a transaction with BEGIN IMMEDIATE
# This prevents the read-modify-write lost update race condition.
conn.execute('BEGIN IMMEDIATE')
for _beg_attempt in range(300):
try:
conn.execute('BEGIN IMMEDIATE')
break
except sqlite3.OperationalError:
import time
time.sleep(0.1)
else:
conn.execute('BEGIN IMMEDIATE')
conn.execute('CREATE TABLE IF NOT EXISTS state (id INTEGER PRIMARY KEY, data TEXT)')
conn.execute('CREATE TABLE IF NOT EXISTS sessions (name TEXT PRIMARY KEY, status TEXT, pane_cwd TEXT, data JSON)')
conn.execute('CREATE INDEX IF NOT EXISTS idx_sessions_pane_cwd ON sessions(pane_cwd)')
@@ -999,6 +1019,11 @@ finally:
if os.path.exists(shm): os.chmod(shm, 0o600)
except Exception:
pass
try:
fcntl.flock(_flock_f, fcntl.LOCK_UN)
_flock_f.close()
except Exception:
pass
PYEOF
}
@@ -1351,9 +1376,13 @@ if not isinstance(ai, dict) or not ai:
db_path = os.path.splitext(yaml_path)[0] + '.db'
if os.path.exists(db_path):
conn = sqlite3.connect(db_path, timeout=60.0)
row = conn.execute('SELECT data FROM state WHERE id=1').fetchone()
if row:
ai = json.loads(row[0]).get('agent_identities') or {}
conn.execute('PRAGMA busy_timeout = 60000')
try:
row = conn.execute('SELECT data FROM state WHERE id=1').fetchone()
if row:
ai = json.loads(row[0]).get('agent_identities') or {}
except sqlite3.OperationalError:
pass
conn.close()
elif os.path.exists(yaml_path):
import yaml
@@ -1689,7 +1718,8 @@ _pane_quiescent() {
local sess="$1" tries="${2:-20}" interval="${3:-0.5}" prev="__none__" cur i
for ((i = 0; i < tries; i++)); do
cur=$(_pane_capture "$sess")
[ -n "$cur" ] && [ "$cur" = "$prev" ] && return 0
[ -z "$cur" ] && { sleep "$interval"; continue; }
[ "$cur" = "$prev" ] && return 0
prev="$cur"
sleep "$interval"
done