Skip to content

Session log improvements + mobile reconnect fix#91

Merged
itscooleric merged 43 commits intomainfrom
feat/session-log-improvements
Mar 18, 2026
Merged

Session log improvements + mobile reconnect fix#91
itscooleric merged 43 commits intomainfrom
feat/session-log-improvements

Conversation

@itscooleric
Copy link
Copy Markdown
Owner

@itscooleric itscooleric commented Mar 18, 2026

Summary

What changed

Area Before After
Services web, shell, claude, copilot, codex, gh web, cli
Session dirs clide-mmprafvt-d85a5cd0 clide-20260318-143022-d85a5cd0
Intercept logs Single global intercept.jsonl Per-session in session dir
Mobile auth ttyd --credential (broken on iOS) Caddy basic_auth → --auth-header
Caddy config In base compose In override only

Key files

  • entrypoint.shTTYD_AUTH_PROXY mode, ping interval tuning, reconnect fix
  • docker-compose.yml — simplified to web + cli, no Caddy dependency
  • docker-compose.override.yml.example — full Caddy setup docs
  • scripts/session-logger.sh — datetime session IDs + CLIDE_SESSION_DIR export
  • scripts/intercept-proxy.py / egress-audit.sh — per-session log output

Test plan

  • Deployed to edge (forge-edge) — mobile working via Caddy
  • Caddy basic_auth + header_up verified (401 without auth, 200 with)
  • Verify session dir names are datetime format after deploy
  • Verify intercept/egress logs land in session dir (not log root)
  • Test ./clide cli and make cli locally
  • Test base compose works standalone (no Caddy, TTYD_USER/TTYD_PASS auth)

🤖 Generated with Claude Code

itscooleric and others added 30 commits March 8, 2026 19:16
groupadd fails with exit 4 if the GID already exists. Fall back to
renaming the existing group to 'clide' so the useradd step succeeds.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the npm-installed Claude Code CLI with the official native
installer (curl -fsSL https://claude.ai/install.sh | sh).  This
eliminates the auto-update permission error that occurred because the
npm global prefix was owned by root while claude runs as clide.

The native binary at ~/.local/bin/claude is self-updating — no more
version pinning or container rebuilds needed to get new Claude releases.
Node.js is retained for Codex CLI and entrypoint config scripts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…41, #42, #44)

Add session-logger.sh that wraps agent CLI sessions with:
- JSONL event logging (session_start/session_end) with schema_version=1
- Raw transcript capture via `script` command, gzipped on exit
- Secret scrubbing (blocklist + heuristic) on all logged output
- Automatic retention: prune oldest sessions beyond CLIDE_MAX_SESSIONS (default 30)
- Agent-agnostic: works with claude, codex, copilot, or any command

Wired into claude-entrypoint.sh so all agent sessions are logged
automatically. Disable with CLIDE_LOG_DISABLED=1.

Adds docs/schema/session-events-v1.md documenting the event format.

Closes #41, closes #42, closes #44

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The install script from claude.ai uses bash syntax (parentheses in
conditionals) which fails under dash (Ubuntu's default sh).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Bump scrollback 10k → 50k (long agent output)
- Zero escape-time (no vim lag)
- Enable focus-events + OSC 52 clipboard
- F12 toggles mouse mode (mobile-friendly)
- Dark theme status bar with active command display
- Subtle pane borders
- Fix reload bind to use ~/.tmux.conf (was /root/)
- ttyd --reconnect 3 for auto-reconnect on disconnect

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds notify.sh — background watcher that tails the transcript and sends
push notifications via ntfy when:
- Agent needs approval (permission prompts)
- Agent needs input
- Errors occur
- Task completes

30s cooldown between notifications to avoid spam. Fully opt-in via
CLIDE_NTFY_URL env var. Works with any self-hosted or public ntfy instance.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ttyd 1.7.7 doesn't have --reconnect as a server flag — it's a
client-side option passed via --client-option reconnect=N.
This was causing exit code 254 crash loops.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Documents how to move auth from ttyd --credential (leaks password to
process args and docker logs) to Caddy reverse proxy layer. Labels
go in docker-compose.override.yml since they can't be conditional.

Co-Authored-By: Claude Opus 4.6 <parameter>noreply@anthropic.com>
ttyd prints --credential args in its startup banner, leaking passwords
into `docker logs`. Filter output through sed to redact TTYD_PASS.
Simpler than moving auth to Caddy (bcrypt $ breaks compose interpolation)
and keeps auth config in one place (.env).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ttyd 1.7.7 prints the credential both as plaintext and base64 in its
startup banner. Scrub both forms.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Piping exec through sed made sed PID 1 instead of ttyd, breaking
health checks and signal handling. Revert to clean exec. ttyd logs
the credential as base64 which is acceptable since docker logs
requires host access. Unset TTYD_PASS from env so child processes
(shells, agents) can't read it.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Downloads and trusts a CA certificate on container startup so internal
TLS services (ntfy, gitlab, etc.) work with https. Graceful — logs a
warning and continues if the download fails. Skips if already installed
(avoids duplicate work when entrypoint.sh calls claude-entrypoint.sh).

Set CLIDE_CA_URL in .env to the URL of your CA cert.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Raw terminal transcripts are too noisy (ANSI escapes, partial writes)
for reliable pattern matching. Replaced background tail -f watcher with
simple event-based calls: start, end, error. Session logger fires
notifications directly at session boundaries.

Approval-prompt notifications will require structured output
(--output-format stream-json or SDK) — tracked as future work.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The web entrypoint runs claude-entrypoint.sh true to pre-seed config.
This was creating spurious sessions and firing false start/end
notifications.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Typing claude/codex/copilot in any shell now automatically goes through
session-logger.sh for structured logging and notifications. No need to
remember session-logger.sh prefix. Disable with CLIDE_LOG_DISABLED=1.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Bump version banner to v4
- Add session logging section with env vars
- Add push notifications (ntfy) section
- Add LAN CA certificate section
- Add F12 mouse toggle to tmux shortcuts
- Document auto-reconnect
- Add session events schema to docs table

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- SC2148: add shellcheck directive to .bashrc
- SC2086: disable for intentional word splitting of AGENT_CMD
- SC2012: disable for intentional ls -1dt sorting by mtime
- MD040: add language to fenced code block in README

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
feat: v4 agent observability — logging, notifications, terminal improvements
Enables SSH-based remote build/deploy workflow from clide to forge-edge
VPS. Previous session added these to the Dockerfile but never committed,
so rebuilds used the stale committed version without these packages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The `script` command captures raw VT100 byte streams which are
unreadable for TUI apps like Claude Code (29K+ ANSI escapes,
15K+ carriage returns, character-by-character cursor positioning).

Instead, harvest Claude Code's native JSONL conversation logs from
~/.claude/projects/<project>/<uuid>.jsonl after session end. These
contain structured user messages, assistant responses (with thinking
blocks), tool_use calls, and tool_results — actually useful data.

Changes:
- Snapshot Claude session files before run, diff after to find new ones
- Symlink conversation.jsonl into clide session directory
- Demote raw script capture to opt-in (CLIDE_RAW_TRANSCRIPT=1)
- Rename transcript.txt → transcript.raw (signals it's not readable)
- session_end event now includes claude_session_id and has_conversation

Addresses #42 (transcript capture) and #41 (structured logging spike).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bake branch@hash (date) into the Docker image via BUILD_VERSION arg,
show it in the CLI splash banner and web entrypoint logs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The session-logger cleanup code (conversation harvesting, session_end
event, transcript compression) was unreachable when the agent was
killed rather than exiting cleanly. This caused events.jsonl to only
contain session_start, conversation.jsonl to never be linked, and
raw transcripts to remain uncompressed.

Move all cleanup into a trap handler for EXIT/INT/TERM/HUP so it
runs regardless of how the session ends. Also show the splash banner
in .bashrc so it appears in the ttyd web terminal on connect.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Instead of waiting until session cleanup to discover and symlink the
Claude Code conversation JSONL, spawn a lightweight background watcher
that polls for up to 60s until the file appears, then links it
immediately. Since Claude Code writes to this file continuously, the
conversation log is readable live throughout the entire session.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Symlinks pointed to paths inside the clide container that don't exist
on the host or in other containers (e.g. Clem file explorer). Now:
- Background watcher copies the file and re-syncs every 30s
- Cleanup does a final copy instead of symlink
- Source path stored in .conv_source for cleanup to find

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Claude Code creates its JSONL session files with 600 permissions.
When session-logger.sh copies them, the restrictive permissions are
preserved, causing CLEM's file explorer to get 403 Forbidden.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
feat: v4 agent observability + SSH remote deploy
itscooleric and others added 13 commits March 16, 2026 18:46
- UID/GID default changed from 1000 to 1100 to avoid conflict with
  Docker group (GID 1000) common on Ubuntu hosts
- CPU limit now configurable via CLIDE_CPUS env var (default: 2.0,
  was hardcoded 4.0 which fails on 2-core VPS)
- Memory limit configurable via CLIDE_MEM_LIMIT (default: 4g)
- Port binding configurable via CLIDE_BIND (default: 0.0.0.0,
  set to Tailscale IP for VPN-only access)
- .env.example updated with all new vars

Closes #82

Co-authored-by: itscooleric <itscooleric@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Parses Claude Code conversation JSONL for token usage data and adds
it to the session_end event in events.jsonl:
- input_tokens, output_tokens, total_tokens
- estimated_cost_usd (per-model pricing: Opus, Sonnet, Haiku)
- turns count

New script: scripts/token-cost.py — standalone parser, can also be
run manually on any conversation.jsonl file.

Closes #43

Co-authored-by: itscooleric <itscooleric@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: outbound connection audit for agent sessions (#51)

New egress-audit.sh daemon monitors /proc/net/tcp and logs all
outbound connections to .clide/logs/egress.jsonl with:
- Remote IP, hostname, port
- Local port, UID
- Timestamp, verdict (allow/reject)

Opt-in via CLIDE_EGRESS_AUDIT=1 in .env. Starts automatically
from firewall.sh when enabled.

Closes #51

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: filter private/Docker IPs from egress audit log

Only log public internet connections — skip 127.x, 172.x, 10.x, 192.168.x

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: itscooleric <itscooleric@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
mitmproxy-based HTTP(S) interceptor that logs all agent traffic to
.clide/logs/intercept.jsonl with:
- Method, URL, host, path, status code
- Request/response headers (secrets auto-redacted)
- Request/response sizes, duration
- Optional body capture (CLIDE_INTERCEPT_BODIES=1)

Opt-in via CLIDE_INTERCEPT=1 in .env. Starts automatically before
the agent in claude-entrypoint.sh, sets HTTP(S)_PROXY env vars so
agent tools route through the proxy transparently.

Includes secret redaction for API keys, tokens, and auth headers.

Closes #50

Co-authored-by: itscooleric <itscooleric@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: ignore-file leakage verification harness + v4 docs (#52)

Test harness to verify AI agents don't leak gitignored secrets:
- setup-fixture.sh: creates synthetic repo with unique markers in
  .env, credentials.json, *.pem, secrets/, node_modules/
- check-leakage.py: scans intercept/egress/conversation logs for
  marker strings — exits 0 (clean) or 1 (leaked)
- run-test.sh: full end-to-end test runner

Also adds docs/observability.md documenting the full v4 stack:
session logging, token tracking, egress audit, intercept proxy,
leakage testing, firewall, MITM cert trust, secret redaction.

Closes #52

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: shellcheck SC2188 — add true before redirection

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: propagate proxy env to ttyd + install mitmproxy CA cert

- Start intercept proxy in entrypoint.sh (not just claude-entrypoint)
  so web terminal sessions also route through the proxy
- Install mitmproxy CA cert system-wide + set NODE_EXTRA_CA_CERTS
  so Node.js (Claude Code) trusts the MITM HTTPS interception
- Wait 2s for mitmproxy to generate its CA before proceeding

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: itscooleric <itscooleric@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…#89, #90)

Intercept and egress logs now write into the per-session directory
instead of a single global file, so they get pruned with session
retention and are easy to correlate. Session directory names use
UTC datetime (clide-20260318-143022-xxxx) instead of base36 timestamps.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Increase reconnect delay from 3s to 10s and ping interval from 5s to
30s. Mobile browsers aggressively kill background WebSockets — the
short ping interval caused the server to drop the connection quickly,
and the tight 3s retry loop created visible flicker without stabilizing.
Both values are now configurable via TTYD_RECONNECT and TTYD_PING_INTERVAL.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
In ttyd 1.7.7, the "reconnect" client option is a DISABLE flag — any
truthy value (like "3" or "10") turns auto-reconnect OFF. The old
config `--client-option reconnect=3` was actually disabling reconnect,
causing "Press Enter to reconnect" on mobile instead of auto-reconnecting.

Fix: remove the reconnect client option entirely (auto-reconnect is on
by default). Only pass it when user explicitly sets TTYD_RECONNECT=0.
Keep the 30s ping interval to give mobile browsers breathing room.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ttyd's built-in basic auth (--credential) is broken on all iOS browsers
and Safari due to Apple's NSURLSession WebSocket implementation not
passing Basic Auth credentials on the WebSocket upgrade request
(ttyd upstream #1437).

Add TTYD_AUTH_PROXY=true mode: delegates authentication to a reverse
proxy (Caddy/nginx) via --auth-header, so ttyd never handles auth
itself and the WebSocket connection works on all browsers.

Also adds caddy network to web service and documents Caddy basic_auth
label setup in docker-compose.yml comments.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Caddy basic_auth validates credentials but doesn't automatically
forward the username. ttyd's --auth-header mode requires the header
to be present. Add header_up to pass {http.auth.user.id} as X-Auth-User.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Base docker-compose.yml now works standalone with ttyd's built-in auth
(no Caddy dependency). Caddy reverse proxy setup (networks, labels,
basic_auth) lives entirely in docker-compose.override.yml, documented
in the .example file.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove individual agent services (claude, copilot, codex, gh, shell).
All CLIs are available from any shell — just type the command. Two
services remain:

- web: browser terminal (ttyd + tmux)
- cli: headless shell (docker compose run --rm cli)

Simplifies Makefile, wrapper script, VS Code tasks, and README.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…auth

Rewrite DEPLOY.md to reflect current setup: two services (web/cli),
Caddy basic_auth proxy for mobile, no more CADDY_HOSTNAME/CADDY_TLS
env vars. Update RUNBOOK.md references from shell/claude to cli.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@itscooleric itscooleric marked this pull request as ready for review March 18, 2026 20:55
@itscooleric itscooleric merged commit bad6fca into main Mar 18, 2026
4 checks passed
@itscooleric itscooleric deleted the feat/session-log-improvements branch March 18, 2026 20:55
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