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):
|
||||
|
||||
Reference in New Issue
Block a user