Bryan's MobaXterm work-box 429s never wrote headers.log because the v0.8.5
gate only fired on (OAuth + unified-*) OR retry-after — and his bare burst
429s carry neither. Detect 429 from the HTTP status line in the -D dump and
ALWAYS write the full raw header block, exempt from the OAuth 50-call cap
(own STATUS_429_HEADER_LOG_LIMIT budget), with a live phi/rl> stderr pointer.
Non-stream path already reached the parser (call_api -D dump); the bug was
the write-gate, not the call. Streaming path shares the same function.
Co-Authored-By: Clover (Claude Opus 4.7) <noreply@anthropic.com>
Root cause: render_status_line suppressed the OAuth line whenever ctx_used,
5h_util, and 7d_util were ALL empty. On a rate-limited session ctx is never
recorded (the error path returns before _record_ctx_used) and pre-v0.8.5 the
unified-* headers weren't captured on errors — so all three stayed empty turn
after turn and the line never appeared on Bryan's work-box. NOT a positioning
bug: the line is a plain printf'd dim line (no scroll-region/cursor escapes)
and is not coupled to streaming or mouse mode.
Fix: suppress only before the first turn (_LARRY_TURNS==0); thereafter always
render — empty fields show "—" placeholders, reset date fills in once headers
populate. /status now renders on demand even pre-first-turn. CR-taint sweep:
coerce_int the reset-epoch arithmetic comparisons + strip_cr the oauth-status
color case (MobaXterm CRLF would otherwise crash/blank the line).
Verify: bash -n clean; 7/7 unit tests (turn-0 suppressed, turn>=1 placeholders,
reset date when populated, renders with LARRY_NO_STREAM=1 + mouse off, survives
CR-tainted epoch, LARRY_NO_STATUS=1 still disables).
Co-Authored-By: Clover (Claude Opus 4.7) <noreply@anthropic.com>
Closes the last gap in the rate-limit-diagnosis pipeline: anthropic-ratelimit-*
headers captured on the MobaXterm work-box now flow to the Mac memory daemon
(Tier 4 Hindsight + Tier 7 mem0) automatically.
- lib/headers-sync.sh: incremental, offset-tracked, idempotent push of
headers.log to ~/.cloverleaf/headers-<hostname>.jsonl on the Mac, riding the
existing authenticated SSH ControlMaster. No new auth; password never in
argv/env. No-op when nothing new; re-seed on local rotation/shrink. Fully
graceful (no target / closed master / transport error → warn + continue;
never crashes the session).
- /headers-sync on|off|status|target <alias>|now slash command + TAB-completion
+ /help. Config persisted to $LARRY_HOME/.env. Auto-sync fires on REPL exit.
- Security: headers.log carries only anthropic-* headers + status lines — NO
PHI per Vera audit V7; transport reused unchanged (not weakened).
Layered cleanly on top of Clover #8's v0.8.5 (4f1ea86) — edits isolated to new
lib + help/array/trap/dispatch hunks; no overlap with the streaming parser,
retry/backoff, error-display, or phi-notice regions.
Co-Authored-By: Clover (Claude Opus 4.7) <noreply@anthropic.com>
Diagnose-don't-assume rate-limit cluster (Clover #8). The rate_limit_error on a
work-box with 90% of the 5h Max quota free was a short-window BURST rail, not 5h
exhaustion — tripped by a stream->non-stream double-send per turn with no backoff.
- Rate-limit backoff honoring retry-after (else exp 2/4/8 cap 30) + actionable
header-parsed message naming the tripped rail; headers.log now captures every
429 (was OAuth+unified-* only), tagged with retry-after + rail.
- parse_stream_to_response detects a non-SSE JSON error body (429/overload) and
returns a distinct code so agent_turn surfaces it WITH backoff instead of
re-sending the whole prompt (single-send invariant). Auto LARRY_NO_STREAM=1 on
MobaXterm/Cygwin/MSYS; explicit LARRY_NO_STREAM=0 still forces streaming on.
- ErrorPI fix: strip_cr on err_type/err_msg in _humanize_api_error (a trailing
CR broke the case match AND carriage-overprinted "API error"); err/warn/log
now strip embedded CRs defensively. (v0.7.5 sweep missed the error-display path.)
- phi tier-5 notice once-per-session via $LARRY_HOME/.phi-notice-shown SESSION_ID
flag (old export flag died in the $(...) subshell -> per-turn nag). Same-pattern
sweep fixed the identical subshell-flag bug in _auto_phi_b64_roundtrip.
Deliverable: Deliverables/2026-05-27-cloverleaf-larry-v085-ratelimit-streaming-fixes.md
Co-Authored-By: Clover (Claude Opus 4.7) <noreply@anthropic.com>
Hardens the installer + auto-updater against the Gitea private-repo trap
(Clover #5 diagnosis): an unauthenticated raw-file read of a sign-in-gated
Gitea returns the HTML Sign-In page at HTTP 200, which `curl -fsSL` treats as
success — so the old code parsed HTML as VERSION/MANIFEST/larry.sh content and
silently aborted (or overwrote real files with HTML). This stranded a work-box
at v0.7.3 until the REQUIRE_SIGNIN_VIEW=false flip.
- New lib/fetch-safe.sh: fetch_validate URL DEST KIND [MAX_TIME]. Detects the
HTML-login trap (DOCTYPE/<html/"Sign In - Gitea"/<title>Sign In markers, or
text/html Content-Type) and validates content shape per file type (semver
VERSION, path-list MANIFEST, shebang larry.sh, non-HTML .sh). On failure:
actionable error + non-zero, target file left untouched.
- install-larry.sh (curl|bash bootstrap) and larry.sh self_update() each carry
a byte-identical inline copy (both run before lib/ can be sourced).
- Every remote-content fetch routed through the validator: install fetch();
agent fetch; sync_from_manifest MANIFEST + per-file; _fetch_with_fallback.
- Optional LARRY_GITEA_TOKEN / GITEA_TOKEN env var adds Authorization: token
<PAT> for authenticated fetch against private repos. Never hardcoded/logged.
Documented in --help + MANUAL.md.
Co-Authored-By: Clover (Claude Opus 4.7) <noreply@anthropic.com>
The slash-command completer (__larry_complete_slash) intentionally appends
a trailing space after a unique match for arg-command ergonomics, but the
main_loop dispatcher matched exact `case` globs — so a completed `/quit `
missed the `/quit)` arm and fell through to "unknown command". Latent since
v0.6.6 (tab completion). Fixed by rtrimming the dispatch key once at the
`case "$input"` boundary, which also transitively protects the sub-command
dispatchers (/origin, /phi-auto, /phi-sidecar, /mouse) that consume the
same $input via _slash_args. Interior `/load FILE` spacing is preserved.
Added a shared rtrim() helper to lib/cygwin-safe.sh next to strip_cr.
Co-Authored-By: Clover (Claude Opus 4.7) <noreply@anthropic.com>
The only path that closes V1 (free-text PHI gap — the dominant real-world
failure mode per Vera). Opt-in install; larry runs in v0.8.1 mode on hosts
without Presidio (MobaXterm/Cygwin per Bryan's accepted tradeoff).
New files:
- lib/phi-presidio-sidecar.py — FastAPI service on 127.0.0.1:$LARRY_PHI_PORT
(default 41189). Presidio AnalyzerEngine + AnonymizerEngine over spaCy
en_core_web_sm + 3 HL7-specific custom recognizers (HL7_MRN, HL7_CARET_NAME,
HL7_PHONE_BARE). POST /redact and GET /health.
- lib/phi-sidecar.sh — lifecycle (start/stop/status/health/ensure). ensure
is idempotent; called backgrounded from main_loop so it never blocks the
first prompt. Honors LARRY_PHI_VENV.
- lib/phi-client.sh — bash client (phi_client_available / phi_redact_text /
phi_redact_entities). CR-safe; 5s timeout bounds tier-5 stall.
larry.sh:
- auto_detect_phi gains tier-5: after tiers 1-4, before status summary,
source phi-client.sh, run Presidio on a token-masked copy of the input,
tokenize each entity through hl7-sanitize.sh tokenize-value (category
presidio_<TYPE>) so token IDs stay stable. Honors confirm + strict modes.
Removed the v0.7.3 early-return that skipped past tier-5 when tiers 1-4
found nothing — pure prose now always reaches tier-5.
- Token-safe substitution: existing [[...]] tokens are pulled to sentinels,
tier-5 value is replaced, sentinels restored — prevents the token-within-
token corruption that naive literal-replace caused on already-tokenized
text. Acronym guard drops HL7/clinical jargon (SSN/MRN/DOB/ADT) Presidio
over-tags as ORGANIZATION.
- Graceful degradation: sidecar unreachable → tier-5 no-ops with a one-time
stderr warning. /phi-sidecar slash command + completion table.
install-larry.sh:
- Probes python3 3.9+; offers to create $LARRY_HOME/phi-venv and install
presidio + fastapi + uvicorn + en_core_web_sm. Skips silently (with a
v0.8.1-mode note) on Cygwin/MobaXterm without python3, and on
non-interactive pipe installs. Sets LARRY_PHI_VENV in the larry shim.
MANIFEST: three new lib files added for auto-sync.
Prototype validation (Bryan's Mac, Apple Silicon, Python 3.14):
cold start (en_core_web_sm): ~9s (vs ~82s if Presidio auto-grabs _lg;
we pin _sm for the REPL budget)
warm analyzer latency: P50 20.6ms / P95 22.7ms
end-to-end HTTP round-trip: ~57ms warm; ~150ms first-post-startup
All comfortably under the 200ms-per-turn budget.
MobaXterm verdict: v0.8.2 is Mac/Linux-only. MobaXterm stays on v0.8.1 +
nudges, per Bryan's explicit acceptance. install-larry.sh enforces this
by platform detection; larry.sh tier-5 silently no-ops when the sidecar
is absent (which IS the MobaXterm path — no code is platform-gated).
Verification: bash -n clean on larry.sh + all 3 new lib scripts; python3
ast.parse clean on the sidecar; end-to-end tier-5 tested live against the
sidecar (pure prose, rule-pack+tier-5 combined with no token corruption,
!nophi bypass); strict-mode fail-closed abort tested; CR-taint, path-block,
and base64 round-trip batteries re-run green.
Co-Authored-By: Clover (Claude Opus 4.7) <noreply@anthropic.com>
Three changes expanding PHI safety envelope on the tool-result surface.
Closes V2 + V12 + V2-sub from Vera's audit. No behavior change for users
not interacting with HL7-shaped data.
- Tool-name allow-list dropped. The v0.7.3 tool-result auto-PHI gate ran
only on read_file (.hl7|.txt), nc_msgs, hl7_field, hl7_diff. v0.8.1
runs _auto_phi_looks_like_hl7 on EVERY tool result. On hit → route
through lib/hl7-sanitize.sh. On miss → pass through unchanged.
Closes V2: bash_exec / ssh_exec / grep_files / read_file of any
extension all get scanned when their output is HL7-shaped. False-
positive cost is negligible (extra regex pass on non-HL7 has zero
behavioral impact).
- Base64-wrapped HL7 round-trip. New _auto_phi_b64_roundtrip helper.
Detects candidate base64 runs (length >= 200, [A-Za-z0-9+/=] only,
length divisible by 4 — NOT entropy-based per Pax §V2-sub: HL7's
repetitive prefixes survive base64 with LOW entropy, so entropy is
the wrong signal). Speculatively decodes each candidate; if decoded
bytes look like HL7, routes through hl7-sanitize.sh and re-encodes
back into the result. Catches ssh_pull_smat sampled mode's TSV
format. Requires python3 (installed everywhere larry runs); skipped
with a one-time stderr warning when unavailable. Server-side TSV
encoding kept (binary-safe transport); client-side unwrap handles
the safety concern, no remote refactor needed.
- Operator review gate for bash_exec/ssh_exec/ssh_pull/ssh_pull_smat
results. When the tool produced HL7-shaped output OR the result
exceeds LARRY_TOOL_RESULT_REVIEW_THRESHOLD bytes (default 8192),
Larry prompts [Y/n/i] before passing the result back to the model.
'i' opens the full output in $PAGER then re-prompts. Default Y
(zero friction). N substitutes a refusal JSON so the model surfaces
that something was withheld. Skipped when LARRY_AUTO_PHI=off (opt-out
consistency) OR no TTY (headless scripts unaffected). Override with
LARRY_TOOL_RESULT_REVIEW=always for paranoid mode. Closes V12.
Proactive same-pattern sweep. Searched for other call sites where tool
output bypasses content-shape gating: only the one in agent_turn. The
v0.8.0-c strict-mode tool-result branch was updated in lockstep so it
now triggers on the broader (content-only) eligibility.
Verification: bash -n clean; b64 round-trip unit-tested with three
cases (real-world HL7 base64 → decoded contains tokenized PHI not
clear-text PHI; plain text → passthrough; non-HL7 b64 → passthrough,
no false positive).
Co-Authored-By: Clover (Claude Opus 4.7) <noreply@anthropic.com>
Three independent zero-risk patches closing V3/V4/V5/V6/V11 gaps from
Vera's static PHI-leak audit. Implemented per Pax's mitigation
recommendations. No new deps, no behavior change for users not handling PHI.
- tool_read_file / tool_grep_files / tool_glob_files / tool_list_dir now
refuse paths under $LARRY_HOME/{log,sanitize,sessions} and
$LARRY_HOME/{.oauth.json,.env} with a structured JSON error the model
must surface. Block-list evaluates at call time; comparison runs against
both the literal and realpath-canonicalized form of both PATH and
$LARRY_HOME. Closes V4 + V6 + V11 (de-sanitization key, OAuth tokens,
PHI clear-text audit log). The proactive same-pattern sweep extended
the block from read_file alone to grep_files/glob_files/list_dir.
- /load <file> pre-routes HL7-shaped content through lib/hl7-sanitize.sh
(segment-aware tokenizer) BEFORE the user_input auto-PHI pass. Closes
V3 — smat dumps loaded via /load no longer rely on the lighter per-word
classifier.
- LARRY_AUTO_PHI=strict (fourth value alongside off/on/confirm) is the
fail-closed mode. Aborts the turn when sanitizer is missing or returns
empty on HL7-shaped content, or when tokenize-value fails. On the
tool-result surface (can't kill an in-flight tool_use), substitutes
the result with a refusal sentinel so raw HL7 NEVER reaches the model.
Existing off/on/confirm semantics unchanged. /phi-auto strict toggle,
/help text, and tests updated. Closes V5.
Refs:
Deliverables/2026-05-27-cloverleaf-larry-phi-leak-audit.md (Vera)
Deliverables/2026-05-27-cloverleaf-larry-phi-mitigation-research.md (Pax)
Verification: bash -n clean; path-block unit-tested with 13 cases including
symlink resolution (file and dir), ../ traversal, nonexistent paths, and
the empty-LARRY_HOME edge case — all pass.
Co-Authored-By: Clover (Claude Opus 4.7) <noreply@anthropic.com>