fix(e2e): finalize sqlite concurrency locking, mock_herdr atomic state, and e2e test suite (100% PASS)
This commit is contained in:
+51
-21
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user