Skip to content

Hook observability: sync activity log, stats.json, agent hooks logs + agent hooks stats#25

Merged
hbrooks merged 1 commit into
mainfrom
hooks-observability
Jul 5, 2026
Merged

Hook observability: sync activity log, stats.json, agent hooks logs + agent hooks stats#25
hbrooks merged 1 commit into
mainfrom
hooks-observability

Conversation

@ellipsis-dev

@ellipsis-dev ellipsis-dev Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Requested by @hbrooks (Slack). In hook mode agent session sync is a quiet no-op on every failure path by design (src/commands/session.tsx), and Claude Code discards async hook output — so a failed background sync was invisible everywhere except (partially) the spool dir. This adds local observability without touching that exit-0 guarantee.

What changed

1. Every sync attempt is logged locally (src/lib/laptop.ts, src/commands/session.tsx)
syncTranscript() appends one JSONL line to $ELLIPSIS_CONFIG_DIR/hooks/sync.log.jsonl per attempt: {ts, outcome, cc_session_id, repo, reason, session_id?, event_count?, error?} with outcome ∈ synced | skipped_unenrolled | not_logged_in | no_transcript | spooled | rejected. Capped at 1000 lines. Recording is best-effort and never throws, so hook mode stays a quiet exit-0 no-op.

2. agent hooks logs--tail N (default 20), --failures (non-synced outcomes only), --json (NDJSON). Answers "did that background process fail, and why".

3. Stats object: $ELLIPSIS_CONFIG_DIR/hooks/stats.json — atomically rewritten (temp file + rename) on every attempt; a plain JSON file anything can read without invoking the CLI:

{
  "last_sync_at": "...", "last_outcome": "synced", "last_error": "...",
  "synced_24h": 12, "failed_24h": 1, "spooled_pending": 0,
  "total_synced": 240, "recent_session_ids": ["ses_..."]
}

24h windows are derived from the log; total_synced carries forward so the log cap can't shrink it. Exposed via agent hooks stats [--json], plus a one-liner in agent hooks status (Last sync 4m ago (synced) · 12 synced / 1 failed in 24h · 0 spooled pending).

Notes

  • skipped_unenrolled is logged but not counted as a failure (it's the consent gate working as intended).
  • spooled_pending is refreshed live from the spool dir when read via the CLI.
  • Smoke-tested manually: hook mode still exits 0 and stays silent on failure paths while logging the outcome; unit tests added in test/laptop.test.ts (not run locally per workflow — typecheck passes).

…stats`

In hook mode `agent session sync` is a quiet no-op on every failure path
by design, which made background sync failures invisible. Now every sync
attempt appends one JSONL line to $ELLIPSIS_CONFIG_DIR/hooks/sync.log.jsonl
(capped, best-effort — a logging failure never breaks the exit-0 guarantee)
and atomically rewrites hooks/stats.json, a plain JSON object anything can
read without invoking the CLI.

- `agent hooks logs [--tail N] [--failures] [--json]`: did the background
  process fail, and why (outcomes: synced / skipped_unenrolled /
  not_logged_in / no_transcript / spooled / rejected)
- `agent hooks stats [--json]`: last sync, last error, 24h synced/failed
  counts, pending spool, total synced, recent v1 session ids
- `agent hooks status`: one-line sync summary appended
@hbrooks hbrooks merged commit 788bb03 into main Jul 5, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant