v0.6: jsonl response path + session memory + global CLI + cron hardening#1
Merged
Conversation
APScheduler 3.x's CronTrigger.from_crontab() interprets numeric day-of-week fields with APScheduler's internal index (0=Mon, 6=Sun) instead of Unix cron (0/7=Sun, 1=Mon, ..., 6=Sat). For users with weekday-only crons like "0 20 * * 1-5", jobs would silently fire Tue-Sat instead of Mon-Fri. Add a build_cron_trigger() helper that translates numeric DoW tokens to APScheduler name form (mon/tue/...) before constructing CronTrigger, then route the four call sites in LiteClaw through it. Discovered when a Mon-Fri 15:20 market-coach cron fired on a Saturday.
…uide Adds first-class macOS support: - liteclaw.py: _recover_proxy() now branches on sys.platform. On Darwin it invokes `launchctl kickstart -k gui/<uid>/com.claude-max-api-proxy` to bounce the LaunchAgent (Docker on macOS cannot read the keychain where the Claude CLI stores OAuth tokens). Linux behavior is unchanged. - start.sh: idempotent launcher that brings up tmux 'claude' (Claude Code CLI) first, waits for the prompt (auto-accepting the "trust this folder" dialog), then starts LiteClaw in tmux 'liteclaw'. Use --attach to land in the claude pane. - MAC-OPS.md: setup, management, and troubleshooting for the LaunchAgent approach, including the IPv4/IPv6 ghost-container hazard. - CLAUDE.md: cross-references MAC-OPS for the macOS auto-recovery and proxy infrastructure paths. - README.md: new Platform Support table making clear that Linux/macOS are supported natively, while Windows requires WSL (LiteClaw depends on tmux). Adds a "One-command launch" subsection pointing at start.sh.
- New bin/liteclaw dispatcher with subcommands: start [--attach] / stop / restart / status / logs / attach / help. Resolves repo root via script location or $LITECLAW_HOME. - setup.sh installs a symlink at ~/.local/bin/liteclaw so the dispatcher is reachable from any directory. - setup.sh also plants sensible ~/.tmux.conf defaults when missing (mouse on, history-limit 50000) so Claude Code panes are scroll-wheel friendly out of the box. - .env.example: new flags BOOT_NOTIFY, LITECLAW_DIR, CLAUDE_CWD, PRIMER_RECENT_TURNS documented for the OpenClaw-style memory layout and boot-ready Telegram ping. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces --continue with --session-id <uuid> using a stable UUID stored in ~/.liteclaw/sessions.json.liteclaw_session_id. Benefits: - Deterministic resume — other Claude Code windows in the same cwd no longer steal which session gets picked up. - Migration-friendly — if sessions.json has no UUID but the target cwd already has exactly one Claude Code session jsonl, start.sh adopts that UUID so existing conversations carry over. - Allocates fresh UUID via `uuidgen` otherwise. CLAUDE_CWD env (default $HOME) pins the working directory so the session log path under ~/.claude/projects/<encoded-cwd>/ is stable regardless of where the user runs `liteclaw start` from. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ening
This is the big one — a set of related changes to (1) stop scraping
tmux for Claude's response, (2) give LiteClaw an OpenClaw-style
persistent memory, and (3) make crons survive more failure modes.
Response source: tmux pane → session JSONL
- _jsonl_path / _record_jsonl_offset / _tail_jsonl_since_offset /
_poll_response_via_jsonl read Claude Code's own session log at
~/.claude/projects/<encoded-cwd>/<session-id>.jsonl and extract
only `text` blocks from assistant messages in the new range.
- Completion detected by stop_reason=end_turn|stop_sequence|
max_tokens. `tool_use` stop_reason means mid-turn; keep waiting.
- File-size-based stable safety net (15 s of no jsonl growth with
some text already captured → assume flushed). Avoids previous
bug where long tool_use chains tripped a text-stable timeout.
- handle_message replaces pane-derived response with jsonl text
when jsonl length ≥ 50% of pane length (guards against returning
a preamble-only stub if tool chain was still running).
- When jsonl delivers a complete turn, _skip_summarizer_once fires
so the summarizer doesn't re-compress clean text, and
_jsonl_delivered_complete suppresses _followup_edit (which was
overwriting delivered messages with "Working… (Ns)" status).
- Mid-poll status edits gated behind SHOW_POLLING_STATUS (off by
default) — these were the source of "Thinking (Crystalizing)…"
leaks into Telegram.
- Fallback to pane-scrape preserved on any jsonl failure.
Memory layout (OpenClaw-inspired):
~/.liteclaw/
transcripts/YYYY-MM-DD.jsonl per-day append-only, schema with
sid:<int> alias (index into
sessions.json.history[]), ~70%
shorter than embedding full UUID
memory/YYYY-MM-DD.md LLM-compacted daily digest
memory/strategic.md optional rolling summary
sessions.json {liteclaw_session_id, history[]}
primer.md assembled at boot (strategic +
recent N turns)
- _migrate_legacy_history splits the old single-file jsonl into
per-day files on first boot.
- _compact_day calls the summarizer (tier-1 proxy, openai-compat)
and writes memory/YYYY-MM-DD.md; idempotent (skip if already
populated).
- _build_primer composes primer.md; falls back to recent daily
memory markdowns when strategic.md is absent.
- _detect_resume_state also maintains sessions.json.history[]
append-only (new UUIDs pushed, prior `ended_at` stamped).
Boot notify + rate limit:
- _send_boot_ready sends a one-shot "🚀 LiteClaw ready" Telegram
message after init, reporting host/target/history/resume/primer.
- Uses sync httpx.Client because it fires before run_polling takes
the event loop.
- 5-minute rate limit via .last_boot_at file so dev/debug restarts
don't spam. Bookkeeping (migrate/compact/primer/history) still
runs even when the ping is suppressed.
Cron hardening:
- _run_cron_job auto-accepts Claude Code's "Yes, I trust" dialog
on first launch in unattended cwds.
- Busy-wait extended 120 s → 300 s with a has_prompt+stable-pane
fallback — addresses the "Session busy for 120s" false fail that
fired on cold Claude launches with chrome glyphs.
- _log_cron_error appends a forensic entry (ts, job config, pane
snapshot) to ~/.liteclaw/cron-error-capture.md on every failure.
- Failure Telegram alert references that file.
is_idle_prompt correctness:
- Removed `·` and `*` from the spinner character set (they are
separators in Claude Code's banner, not real frames) and
anchored _ACTIVITY_PATTERNS to line start. Without this, the
banner "· Claude Max" line matched as "activity" forever and
is_idle_prompt never returned True.
/recall session:
- Session-scoped recall. `/recall session` restricts to current
session; `/recall session <uuid>` targets a specific past one.
- Filter accepts both legacy full-UUID session_id entries and new
sid:<int> aliases for back-compat.
Misc:
- USE_JSONL_RESPONSE / SHOW_POLLING_STATUS / BOOT_NOTIFY /
LITECLAW_DIR / CLAUDE_CWD / PRIMER_RECENT_TURNS env flags.
- Summarizer prompt gains a PRESERVE-AS-IS block for numbered /
lettered option lists so "B / C / D 선택" messages don't lose
their option bodies in the tmux-pane path.
- Placeholder text changed from "📤 Sent. Waiting for response..."
to "⏳ 작업 중…".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- New "What's new in v0.6 (April 2026)" section (EN + KO) covering: global CLI, --session-id pinning, jsonl response path, OpenClaw- style memory layout, boot-ready ping, cron hardening, /recall session, quieter Telegram UX. - Configuration table gains BOOT_NOTIFY, LITECLAW_DIR, CLAUDE_CWD, PRIMER_RECENT_TURNS, USE_JSONL_RESPONSE, SHOW_POLLING_STATUS. - Quick Start documents `liteclaw start|stop|...` as the preferred entry point (global CLI via ~/.local/bin/liteclaw symlink). - Notes that the underlying start.sh and manual flow still work. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New command to append user-provided instructions to the summarizer's
system prompt at runtime, without code edits. Useful when the user
wants to tune summarization behavior ("never drop numbered option
lists", "always preserve code blocks verbatim", etc.) from Telegram.
Commands:
/tell-summarizer <instruction> append to extra prompt (in-memory)
/tell-summarizer show display current extras
/tell-summarizer clear wipe extras
/tellsum short alias
Persistence: in-memory only. Resets on daemon restart. This is
intentional — runtime tuning should not quietly leak across restarts.
Integration point: `_summarize` appends
`self._summarizer_extra_prompt` to `SUMMARIZE_PROMPT` under a
"USER-PROVIDED RUNTIME INSTRUCTIONS" header before sending to the
tier-1 proxy.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- README.md / README_KO.md: shields.io badges (License, Python,
Platform, CI, GitHub stars) at the top.
- .github/workflows/ci.yml: minimal but meaningful checks
- liteclaw.py AST parse
- bash -n on setup.sh / start.sh / bin/liteclaw
- requirements.txt install + import check (httpx, telegram,
dotenv, apscheduler)
- advisory ruff lint (continue-on-error until codebase catches up)
- .github/ISSUE_TEMPLATE/bug_report.md and feature_request.md with
LiteClaw-specific prompts (env versions, cron-error-capture.md
reference, scope checkboxes).
- CONTRIBUTING.md: ground rules (single-file core, no new API key,
fallbacks non-negotiable), project structure, commit style,
release flow. Calls out that Telegram-side UX regressions are
easy to miss in code review and asks PRs to attach a
before/after transcript when relevant.
No runtime behavior change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds two layers of protection so user replies don't get silently lost
when the daemon is restarted (intentional or crash) while a poll task
is in flight:
1. Persistent pending replies (~/.liteclaw/pending_replies.json)
- Every user message with a placeholder sent but no reply yet is
recorded with: user_msg_id, placeholder_msg_id, chat_id, jsonl
offset, sent timestamp, target.
- On daemon boot, _resume_pending_on_boot (registered as
Application.post_init) walks the file:
* if the session jsonl now has a complete turn → edit the
placeholder in-place with the answer
* else if the entry is older than PENDING_MAX_AGE_SEC (default
900 s) → mark the placeholder as "재기동으로 유실됨"
* else → spawn a background polling task that completes the
delivery later
- Survives crashes / SIGKILL / power loss because state lives on disk.
2. Graceful shutdown
- Application.run_polling now passes
stop_signals=(SIGINT, SIGTERM) explicitly so pkill / launchctl
bootout trigger PTB's in-flight handler drain instead of cutting
mid-update.
Behavioral changes:
- handle_message now records pending right after sending the
placeholder and clears it after _deliver_response returns.
- All bookkeeping is best-effort; failures log a warning and never
block the user-facing path.
New env: PENDING_MAX_AGE_SEC (default 900) — entries older than this
on boot are abandoned with a "lost" notice rather than re-polled.
Also adds CHANGELOG.md covering this work plus the v0.6.1 fixes done
in the same session (cron path fix, market-coach timeout bumps,
launchd TZ fix, okl-ear path + buffering fix, knowledge.json prompt
injection, /tell-summarizer command, battery morning check cron).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brings the project CHANGELOG.md up to date. v0.6.0 summarizes the work shipped in PR #1 (jsonl response path, session memory, global CLI, cron hardening); v0.6.1 captures the follow-up work from the 2026-04-23 session: - daemon-restart resilience (persistent pending_replies + graceful shutdown) - /tell-summarizer (/tellsum) runtime control over summarizer prompt - battery-morning-check cron for 삼성SDI / 에코프로 (parallel to the existing taihan-morning-check) - community hygiene set (CONTRIBUTING, CI workflow, issue templates, README badges) - path fixes: ~/.openclaw/... → ~/projects/openclaw-archive/... on 5 cron jobs - timeout bumps: market-coach-* 300→600 s, taihan-morning-check 900→1200 s - schedule shift: market-coach-preopen 08:03 → 07:45 KST (before NXT) - macOS launchd TZ handling: explicit Asia/Seoul via setenv-tz LaunchAgent + LimitLoadToSessionType broadening so agents fire during display sleep - okl-ear-report: hardcoded Linux .env path → Path.home(), `-u` on Python for real-time log flush, knowledge.json injected into LLM prompt so participant names are resolved (불명확 36→24 measured) - SUMMARIZE_PROMPT preserves numbered/lettered option lists verbatim Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous CHANGELOG v0.6.1 entry leaked details about my personal Mac mini setup (okl-ear / autotrade / LaunchAgent TZ / knowledge.json / cron config references) that are not part of the LiteClaw bridge itself. Those belong in DEVNOTES.md (which is gitignored). Keep in CHANGELOG (actual LiteClaw code/doc changes): - daemon-restart resilience (pending_replies.json + post_init resume + explicit stop_signals) - /tell-summarizer (/tellsum) runtime summarizer prompt control - community hygiene set (CONTRIBUTING, CI workflow, issue templates, README badges) - SUMMARIZE_PROMPT PRESERVE-AS-IS block for option lists Removed from CHANGELOG (moved to DEVNOTES.md privately): - .cron_jobs.json path/timeout/schedule tweaks (my personal crons) - LaunchAgent TZ + LimitLoadToSessionType fixes (my OS-level config) - okl-ear-report path + -u + knowledge.json injection (separate project) - battery-morning-check cron specifics (my workflow) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a new `type: "subprocess"` variant to `.cron_jobs.json`. When set, `_run_cron_job` dispatches to `_run_subprocess_cron`, which: - Runs `job["command"]` via `asyncio.create_subprocess_shell` under `job["project"]` cwd, capturing combined stdout+stderr. - Enforces `job["timeout"]` (default 1200 s) by killing the process. - Persists `last_run` / `last_status` like regular cron jobs. - Optionally sends a compact Telegram summary (tail N lines, controlled by `notify_telegram` and `notify_tail_lines`). - On non-zero exit or timeout, writes to ~/.liteclaw/cron-error-capture.md and fires a ❌ Telegram alert. Motivation: on macOS, LaunchAgents with StartCalendarInterval are gated by `com.apple.UserEventAgent-Aqua`. When the GUI session isn't active (display lock, fast-user-switch, brief inactivity) the job silently skips. LaunchAgents are the wrong tool for reliability on a headless-ish workstation. LiteClaw's APScheduler runs inside the always-on daemon so the job fires regardless of GUI state. The historical `type: "claude"` path (tmux + Claude Code LLM cron) remains the default and is untouched. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR consolidates the
feat/macos-supportline of work intomain. It beganas a macOS port of the bridge (
start.sh, LaunchAgent proxy) and grew into areliability + UX overhaul that benefits all platforms.
Commits (oldest → newest)
[fix] cron day-of-week— APScheduler was interpreting1-5as Tue-Sat; corrected to Unix Mon-Fri semantics.[macOS] platform-aware proxy recovery + start.sh launcher + MAC-OPS— Docker on Mac can't read keychain; LaunchAgent instead.[feat] global liteclaw CLI + setup.sh enhancements—bin/liteclaw+ symlink install +.tmux.confdefaults.[feat] start.sh --session-id <uuid>— deterministic session pinning.[feat] jsonl response path + session memory + boot notify + cron hardening— the big one (see below).[docs] README + README_KO v0.6[feat] /tell-summarizer— runtime control over summarizer system prompt.[chore] community hygiene— badges, CI, issue templates, CONTRIBUTING.md.Headline changes
Response source: tmux pane → session JSONL
Instead of scraping the tmux pane (brittle: ANSI chrome leaks, scrollback truncation, summarizer over-compression), the daemon now reads Claude Code's own session log at
~/.claude/projects/<encoded-cwd>/<session-id>.jsonland extracts cleantextblocks. Completion viastop_reason=end_turn. Automatic fallback to pane-scrape on jsonl failure.OpenClaw-style memory layout (
~/.liteclaw/)Per-day transcripts, daily markdown digests (LLM-compacted), rolling strategic summary, and a startup primer assembled from recent + strategic context. Legacy
~/.liteclaw-history.jsonlauto-migrates. Transcripts use an integersidalias (index intosessions.json.history[]) — ~70% shorter per row than embedding full UUIDs.Cron hardening
is_idle_promptfalse-positive on UI chrome (removed·/*from spinner set, anchored pattern to line start)~/.liteclaw/cron-error-capture.mdTelegram UX
BOOT_NOTIFY: one-shot "ready" ping after init with 5-min rate limitSHOW_POLLING_STATUS(off by default) — these were the source of "Thinking (Crystalizing)…" leaks_followup_editskipped when jsonl returned a complete turn — prevents delivered message being overwritten by stale status edit/tell-summarizer <instruction>for runtime summarizer tuning from TelegramGlobal CLI
liteclaw start|stop|restart|status|logs|attachworks from any directory (setup.sh symlinks to~/.local/bin/liteclaw). Underlyingstart.shstill works.Test plan
bash -n setup.sh start.sh bin/liteclaw— cleanpython3 -c "import ast; ast.parse(open('liteclaw.py').read())"— cleanliteclaw start, Telegram roundtrip delivers via jsonlliteclaw stop / status / logs / attach_compact_dayproduces dailymemory/*.mdUser-visible diff on Telegram
Before: long answers truncated mid-chunk, "Thinking (Crystalizing)…" sometimes leaked, placeholder occasionally overwritten by short status edit after the real answer arrived.
After: one placeholder ("⏳ 작업 중…") edit-in-place to the full final text, no mid-poll noise, no summarizer over-compression when jsonl path succeeds.
Config migration
New
.envkeys (all optional, defaults preserve prior behavior):BOOT_NOTIFY(default1)LITECLAW_DIR(default~/.liteclaw)CLAUDE_CWD(default$HOME)PRIMER_RECENT_TURNS(default20)USE_JSONL_RESPONSE(default1)SHOW_POLLING_STATUS(default0)🤖 Generated with Claude Code