fix(b4): restore dynamic POSIX timestamp in herdr ls shim and reconcile.sh (100% PASS)
This commit is contained in:
+108
-11
@@ -543,20 +543,111 @@ except Exception:
|
||||
rm -f "$wrapper_dir/$buf"
|
||||
;;
|
||||
ls)
|
||||
_real_herdr agent list 2>/dev/null | python3 -c "
|
||||
import sys, json
|
||||
format=""
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
-F)
|
||||
if [ $# -lt 2 ]; then
|
||||
echo "Error: -F requires a value" >&2
|
||||
exit 1
|
||||
fi
|
||||
format="$2"
|
||||
shift 2
|
||||
;;
|
||||
*) shift ;;
|
||||
esac
|
||||
done
|
||||
# herdr has no session-creation timestamp: `agent list`, `agent get`,
|
||||
# `pane get` and `api snapshot` all lack one. Derive it from the OS as the
|
||||
# start time of the pane's ROOT process (`shell_pid`).
|
||||
#
|
||||
# It must NOT come from `foreground_processes[0]`: under MAM that slot
|
||||
# holds the `caffeinate -i -t 300` keep-awake wrapper, which respawns every
|
||||
# five minutes. Its start time creeps forward, so a session idle for longer
|
||||
# than one caffeinate cycle would look "created after" its own transcript
|
||||
# and be discarded by the stale-transcript guard in verify_session_uuid.
|
||||
#
|
||||
# This runs as ONE python process that re-implements `_real_herdr` and
|
||||
# batches a single `ps` over every pid, rather than forking python and ps
|
||||
# once per agent inside a shell loop: reconcile calls this on a 15s
|
||||
# heartbeat, so per-agent forking shows up as a standing cost.
|
||||
#
|
||||
# TZ=UTC pins both `ps` (which prints lstart in the caller's zone) and
|
||||
# `mktime` (which reads it back in the caller's zone) to the same zone.
|
||||
# They already agree at any single TZ, so this is not about skew between
|
||||
# them -- it removes the once-a-year DST ambiguity in local-time mktime.
|
||||
# LC_ALL=C pins lstart's field names, which are locale-dependent.
|
||||
_sess="${HERDR_SESSION_NAME:-}"
|
||||
if [ "$_sess" = "default" ]; then
|
||||
_sess=""
|
||||
fi
|
||||
_real_herdr agent list 2>/dev/null | \
|
||||
MAM_LS_FORMAT="$format" MAM_REAL_HERDR="$REAL_HERDR" MAM_LS_SESSION="$_sess" \
|
||||
TZ=UTC LC_ALL=C python3 -c '
|
||||
import json, os, subprocess, sys, time
|
||||
|
||||
fmt = os.environ.get("MAM_LS_FORMAT", "")
|
||||
real = os.environ.get("MAM_REAL_HERDR") or "herdr"
|
||||
sess = os.environ.get("MAM_LS_SESSION") or ""
|
||||
base = [real] + (["--session", sess] if sess else [])
|
||||
|
||||
raw = sys.stdin.read()
|
||||
try:
|
||||
data = json.load(sys.stdin)
|
||||
res = data.get('result', data)
|
||||
for a in res.get('agents', []):
|
||||
agents = json.loads(raw).get("result", {}).get("agents", []) or []
|
||||
except Exception:
|
||||
# Unparseable means "we do not know", not "there are no sessions".
|
||||
# Exiting nonzero lets reconcile fall into its herdr_confirmed=False path
|
||||
# instead of reading empty stdout as a confirmed zero and terminating
|
||||
# every live row it has on file.
|
||||
sys.exit(1)
|
||||
|
||||
rows = []
|
||||
for a in agents:
|
||||
name = a.get("name") or a.get("agent") or "unknown"
|
||||
pane = a.get("pane_id") or ""
|
||||
pid = None
|
||||
if pane:
|
||||
try:
|
||||
name = a.get('name') or a.get('agent') or 'unknown'
|
||||
print(f\"{name}|999999\")
|
||||
out = subprocess.run(base + ["pane", "process-info", "--pane", pane],
|
||||
capture_output=True, text=True, timeout=10).stdout
|
||||
pi = json.loads(out).get("result", {}).get("process_info", {})
|
||||
p = pi.get("shell_pid") or pi.get("foreground_process_group_id")
|
||||
if isinstance(p, int) and p > 0:
|
||||
pid = p
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
" || true
|
||||
rows.append((name, pid))
|
||||
|
||||
starts = {}
|
||||
pids = sorted({p for _, p in rows if p})
|
||||
if pids:
|
||||
try:
|
||||
out = subprocess.run(["ps", "-o", "pid=,lstart=", "-p", ",".join(str(p) for p in pids)],
|
||||
capture_output=True, text=True, timeout=10).stdout
|
||||
for line in out.splitlines():
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
head, _, rest = line.partition(" ")
|
||||
try:
|
||||
starts[int(head)] = int(time.mktime(time.strptime(rest.strip(), "%a %b %d %H:%M:%S %Y")))
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Degrade to now, never to 0 or a sentinel. The consumer guard is
|
||||
# if epoch and mtime(transcript) < epoch: reject
|
||||
# so an over-estimate only makes it stricter, while an under-estimate
|
||||
# (0 is falsy; 999999 is 1970-01-12 and below every real mtime) switches
|
||||
# the guard off outright -- which is the B-4 defect.
|
||||
now = int(time.time())
|
||||
for name, pid in rows:
|
||||
if fmt == "#{session_name}":
|
||||
print(name)
|
||||
else:
|
||||
print(name + "|" + str(starts.get(pid) or now))
|
||||
'
|
||||
;;
|
||||
*)
|
||||
_real_herdr "$cmd" "$@"
|
||||
@@ -1042,7 +1133,13 @@ def verify_session_uuid(ws, agent, uuid, row=None, home_dir=None, claude_dir=Non
|
||||
home = home_dir or os.environ.get("HOME_DIR", "")
|
||||
c_dir = claude_dir or os.environ.get("CLAUDE_PROJECT_DIR", f"{home}/.claude/projects")
|
||||
row = row or {}
|
||||
epoch = row.get("herdr_session_epoch", 0)
|
||||
# The floor answers "could this transcript belong to a PREVIOUS incarnation
|
||||
# of this session?", which only matters while picking an unknown uuid off
|
||||
# disk. In "revalidate" the uuid is one this row already recorded, and a
|
||||
# session resumed but not yet written to legitimately has a transcript
|
||||
# older than its current process -- applying the floor there discards the
|
||||
# very conversation the resume was for.
|
||||
epoch = row.get("herdr_session_epoch", 0) if mode == "discover" else 0
|
||||
cwd = row.get("pane", {}).get("cwd", "") or ws
|
||||
|
||||
if workspace_key(cwd) != workspace_key(ws):
|
||||
|
||||
@@ -329,24 +329,35 @@ claude_project_dir = os.environ.get('CLAUDE_PROJECT_DIR', f"{home}/.claude/proje
|
||||
|
||||
now_iso = datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')
|
||||
|
||||
# Bound unconditionally. This used to be assigned only inside the "except
|
||||
# NameError" branch below, which the write path never enters because
|
||||
# atomic_dump_yaml predefines `d` -- so drift C's pin raised
|
||||
# NameError: name 'lib_sh' is not defined and aborted the whole sweep,
|
||||
# in write mode only.
|
||||
lib_sh = os.environ.get('LIB_SH')
|
||||
if not lib_sh:
|
||||
_ws_root = os.environ.get('WORKSPACE_ROOT')
|
||||
if not _ws_root:
|
||||
_ws_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../..'))
|
||||
lib_sh = os.path.join(_ws_root, '.agents/skills/lib.sh')
|
||||
|
||||
try:
|
||||
d
|
||||
except NameError:
|
||||
import subprocess
|
||||
d = {}
|
||||
try:
|
||||
lib_sh = os.environ.get('LIB_SH')
|
||||
if not lib_sh:
|
||||
ws_root = os.environ.get('WORKSPACE_ROOT')
|
||||
if not ws_root:
|
||||
ws_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../..'))
|
||||
lib_sh = os.path.join(ws_root, '.agents/skills/lib.sh')
|
||||
script = f"source '{lib_sh}' && load_state_json"
|
||||
out = subprocess.check_output(['bash', '-c', script], stderr=subprocess.DEVNULL)
|
||||
d = json.loads(out.decode('utf-8'))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Any herdr_session_epoch at or below this is not a real session time.
|
||||
# 1000000000 = 2001-09-09; it is below every plausible MAM session and far
|
||||
# above the 0 / 999999 sentinels this guard exists to reject.
|
||||
MAM_EPOCH_FLOOR = 1000000000
|
||||
|
||||
drifts = []
|
||||
actions = []
|
||||
|
||||
@@ -375,10 +386,24 @@ try:
|
||||
sys.stderr.write(f"LS CMD: {cmd} | RC: {r.returncode} | STDOUT: {r.stdout} | STDERR: {r.stderr}\n")
|
||||
if r.returncode == 0:
|
||||
for line in r.stdout.strip().split('\n'):
|
||||
if not line:
|
||||
if not line or '|' not in line:
|
||||
continue
|
||||
name, created = line.split('|', 1)
|
||||
herdr_sessions.append({'name': name, 'created': int(created), 'server': srv})
|
||||
# A malformed field must not abort the whole sweep: the old
|
||||
# bare int() raised out of the enclosing try and flipped
|
||||
# herdr_confirmed to False for every server, which reads as
|
||||
# "herdr is down" and suppresses drift detection entirely.
|
||||
try:
|
||||
created_i = int(created.strip())
|
||||
except ValueError:
|
||||
created_i = 0
|
||||
# Below the floor means "no real creation time" -- 0, or the
|
||||
# 999999 sentinel the shim used to emit (B-4). Both are far
|
||||
# below any live transcript's mtime, so they silently disable
|
||||
# the stale-transcript guard in verify_session_uuid.
|
||||
if created_i < MAM_EPOCH_FLOOR:
|
||||
created_i = 0
|
||||
herdr_sessions.append({'name': name, 'created': created_i, 'server': srv})
|
||||
else:
|
||||
err = (r.stderr or '').lower()
|
||||
is_empty = ('no server running' in err) or ('no sessions' in err) or ('failed to connect' in err)
|
||||
@@ -494,11 +519,18 @@ if herdr_confirmed:
|
||||
elif agent == 'cline':
|
||||
cmd_full = 'cline -i'
|
||||
server_opt = f"-L {srv} " if srv != 'default' else ""
|
||||
# The shim resolves this from the pane's root process. Fall back to now
|
||||
# only if that failed: 'now' can merely over-estimate creation time,
|
||||
# which tightens the stale-transcript guard, whereas 0 disables it and
|
||||
# stamps a 1970-01-01 herdr_session_created_at into the YAML.
|
||||
created_epoch = t.get('created') or 0
|
||||
if created_epoch < MAM_EPOCH_FLOOR:
|
||||
created_epoch = int(time.time())
|
||||
entry = {
|
||||
'name': name,
|
||||
'status': 'running',
|
||||
'herdr_session_created_at': datetime.fromtimestamp(t.get('created', 0), tz=timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ'),
|
||||
'herdr_session_epoch': t.get('created', 0),
|
||||
'herdr_session_created_at': datetime.fromtimestamp(created_epoch, tz=timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ'),
|
||||
'herdr_session_epoch': created_epoch,
|
||||
'herdr_session': srv,
|
||||
'pane': {'index': 0, 'pid': pm['pid'], 'cmd': agent, 'cmd_full': cmd_full, 'cwd': pm['cwd']},
|
||||
'start_command': f'HERDR_SESSION_NAME={srv} herdr new-session -d -s "{name}" -x 140 -y 40 -c "{pm["cwd"]}" "{cmd_full}"',
|
||||
|
||||
Reference in New Issue
Block a user