Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ specs/
build/
dist/
*.egg-info/
.claude/
5 changes: 3 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ Main file: `new_session.py`. No dependencies beyond Python stdlib.
- **Phase 1**: Launch new terminal tab with `claude '<prompt>'`
- **Phase 1b**: Wait for new Claude process (process count check, 15s timeout)
- **Phase 2**: Verify new session is active (transcript file growth, configurable timeout)
- **Kill**: Close old tab's shell process tree (detached subprocess on Windows, SIGTERM on Unix)
- **Kill**: Close old tab's shell process tree (detached subprocess on Windows, SIGTERM on Unix/WSL)
- **WSL detection**: Auto-detects WSL2 via `/proc/version`, launches tabs via `wt.exe` interop, uses `claude.exe` if native `claude` isn't installed. Recognizes WSL process names (`relay`, `sessionleader`) in the process tree.

## Integration

Expand Down Expand Up @@ -53,6 +54,6 @@ python3 new_session.py --project-dir /path/to/project --prompt "task" --no-close
## Testing

```bash
python scripts/test.py # 70 tests
python scripts/test.py # 125 tests
python new_session.py --project-dir . --dry-run # verify command without executing
```
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,15 @@ If any step fails, the old tab is preserved. Nothing is lost.
| Platform | Terminal | Tab launch | Tab color | Tab title | Kill method |
|----------|----------|-----------|-----------|-----------|-------------|
| Windows | Windows Terminal | `wt new-tab` | Yes | Yes | `taskkill /F /T` (detached) |
| WSL2 | Windows Terminal | `wt.exe new-tab` via interop | Yes | Yes | `SIGTERM` to process group |
| macOS | Terminal.app | `osascript` | No | No | `SIGTERM` to process group |
| Linux | gnome-terminal | `gnome-terminal --tab` | No | Yes | `SIGTERM` to process group |
| Linux (fallback) | any | background process | No | No | `SIGTERM` to process group |

### WSL2 details

WSL2 is auto-detected via `/proc/version`. The script calls `wt.exe` through Windows interop to open a new Windows Terminal tab running the same WSL distro. Claude is launched as `claude` (if installed natively in WSL via npm) or `claude.exe` (Windows Claude via interop). Process management uses native Linux tools (`ps`, `kill`). WSL-specific process names (`relay`, `sessionleader`) are recognized in the process tree.

## Usage

```bash
Expand Down Expand Up @@ -179,8 +184,9 @@ Use `--close-tab` to auto-close: temporarily sets `closeOnExit=always`, kills th
## Requirements

- Python 3.8+
- Claude Code CLI (`claude`) in PATH
- Claude Code CLI (`claude`) in PATH (or `claude.exe` via Windows interop in WSL)
- **Windows**: Windows Terminal (ships with Windows 11, available for Windows 10)
- **WSL2**: Windows Terminal + `wt.exe` available via interop (automatic if WT is installed)
- **macOS**: Terminal.app (default) or iTerm2
- **Linux**: gnome-terminal recommended; falls back to background process

Expand All @@ -196,7 +202,7 @@ python scripts/test.py
new_session.py # Main script — session launcher and state handoff
context_reset.py # Backward-compat alias (imports new_session.py)
task_claims.py # Multi-tab task negotiation with OS-level file locks
scripts/test.py # Tests for new_session (62 tests)
scripts/test.py # Tests for new_session (125 tests)
scripts/test_task_claims.py # Tests for task_claims (35 tests)
~/.claude/context-reset/ # Runtime data (logs, color map)
SESSION_STATE.md # Auto-generated in target project (gitignored)
Expand Down
20 changes: 10 additions & 10 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,15 +173,15 @@ Full Mac, WSL, and Linux support for the entire Claude Code management system

Goal: share this system with others who aren't on Windows Terminal.

- [ ] T001: Audit all scripts for Windows-only assumptions (wt, powershell, C:\ paths, taskkill)
- [ ] T002: openclaw-checkin.py — make paths portable (no hardcoded C:\Users\joelg paths)
- [ ] T003: stop-message.txt — use env vars / relative paths instead of absolute Windows paths
- [ ] T004: WSL support — detect WSL and route through wt.exe (WSL can call Windows executables)
- [ ] T005: Mac support — Terminal.app / iTerm2 tab management (osascript exists but untested end-to-end)
- [ ] T006: Linux support — gnome-terminal / tmux / screen session management
- [ ] T007: Auto-detect platform and select correct launch method without user config
- [x] T001: Audit all scripts — 18 Windows patterns found, all gated behind IS_WIN. No unguarded assumptions.
- [x] T002: openclaw-checkin.py — make paths portable (TRACKER_PATH via env var, Path.home() default)
- [x] T003: stop-message.txt — use env vars ($OPENCLAW_CHECKIN_PY, $CONTEXT_RESET_PY, $NEW_SESSION_PY)
- [x] T004: WSL support — detect WSL via /proc/version, route through wt.exe interop, 14 new tests
- [x] T005: Mac support — osascript launch tested via mocked platform flags (6 tests). E2E needs Mac hardware (T008).
- [x] T006: Linux support — gnome-terminal + fallback tested via mocked platform flags (9 tests). E2E needs Linux (T009).
- [x] T007: Auto-detect platform — IS_WIN/IS_WSL/IS_MAC/Linux chain in build_launch_cmd, no user config needed
- [ ] T008: Test end-to-end on Mac (need a Mac tester or CI)
- [ ] T009: Test end-to-end on native Linux (gnome-terminal)
- [ ] T010: Test end-to-end on WSL2 (route through Windows Terminal)
- [ ] T011: Update README with cross-platform install + usage docs
- [ ] T012: Package for pip install with platform-appropriate defaults
- [x] T010: WSL2 dry-run verified — detection, wt.exe cmd, claude.exe fallback, shell PID found via relay process
- [x] T011: README updated — WSL2 row in platform table, WSL details section, requirements, test count
- [x] T012: pip install verified — pyproject.toml already platform-agnostic, no changes needed
167 changes: 167 additions & 0 deletions docs/FEEDBACK-LOOP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
# Claude Code → OpenClaw Feedback Loop

Documents the full architecture for how Claude Code sessions report status back to OpenClaw.

---

## Overview

When Claude Code finishes a context reset (or sends a manual status update), it fires a
stop hook that flows through a chain of components to update both the local tab tracker
and the OpenClaw main session.

---

## Architecture Diagram

```
┌─────────────────────────────────────────────────────────────────────────┐
│ Windows (Claude Code, hook runner) │
│ │
│ Claude Code session ends / stop hook fires │
│ │ │
│ ▼ │
│ openclaw-checkin.js (Stop hook module, Windows-side JS shim) │
│ C:\Users\joelg\.claude\hooks\run-modules\Stop\openclaw-checkin.js │
│ │ │
│ │ spawns: wsl -e bash -c "python3 <path> --status done ..." │
│ │ env: CLAUDE_PROJECT_DIR set by hook runner │
│ ▼ │
│ openclaw-checkin.py (WSL/Linux Python — runs in WSL context) │
│ /mnt/c/Users/joelg/.claude/scripts/openclaw-checkin.py │
│ (mirror of context-reset/scripts/openclaw-checkin.py) │
│ │ │
│ ├──[1] tracker update (FAST — local file, no network) ─────────► │
│ │ /home/ubu/.openclaw/workspace/scripts/claude-tabs/ │
│ │ tracker.json │
│ │ • appends to checkins[] │
│ │ • updates last_checkin │
│ │ • if status==done: sets status=completed + summary │
│ │ • atomic write (tmp → rename) │
│ │ │
│ ├──[2] comms log (FAST — local append) ───────────────────────► │
│ │ ~/.openclaw/comms/claude-code.jsonl │
│ │ • every checkin logged with ts, status, latency, result │
│ │ │
│ └──[3] OpenClaw chat API (SLOW — may timeout) ──────────────── ► │
│ http://localhost:18789/v1/chat/completions │
│ • fire-and-forget (5s timeout) │
│ • LLM round-trip is slow; timeouts are expected/OK │
│ • useful for real-time notifications when it works │
└─────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────┐
│ OpenClaw monitor side (Linux/WSL) │
│ │
│ claude-tab-monitor cron (every 30 minutes) │
│ │ │
│ │ reads │
│ ▼ │
│ tracker.json ◄──── updated by openclaw-checkin.py [1] │
│ /home/ubu/.openclaw/workspace/scripts/claude-tabs/tracker.json │
│ │ │
│ ▼ │
│ manage-claude-code.py (monitor subcommand) │
│ /home/ubu/.openclaw/workspace/scripts/claude-tabs/manage-claude-code.py│
│ │ │
│ ▼ │
│ Reports stalls, deaths, completions to main OpenClaw session / Slack │
└─────────────────────────────────────────────────────────────────────────┘
```

---

## Component Locations

| Component | Path |
|-----------|------|
| Stop hook module (JS, Windows) | `C:\Users\joelg\.claude\hooks\run-modules\Stop\openclaw-checkin.js` |
| Checkin script (Python, WSL canonical) | `/mnt/c/Users/joelg/.claude/scripts/openclaw-checkin.py` |
| Checkin script (Python, source repo) | `/mnt/c/Users/joelg/Documents/ProjectsCL1/_grobomo/context-reset/scripts/openclaw-checkin.py` |
| Comms log (JSONL audit trail) | `~/.openclaw/comms/claude-code.jsonl` |
| Tab tracker | `/home/ubu/.openclaw/workspace/scripts/claude-tabs/tracker.json` |
| Tab manager | `/home/ubu/.openclaw/workspace/scripts/claude-tabs/manage-claude-code.py` |

> **Note:** The `.claude/scripts/` copy and the context-reset `scripts/` copy are identical files.
> When updating `openclaw-checkin.py`, sync both with `cp`.

---

## Data Flow — Step by Step

1. **Claude Code session ends** (or checkin is called manually mid-session)
2. **`openclaw-checkin.js`** spawns a WSL process: `wsl -e bash -c "python3 <path> --status done --detail 'Session stop event' --project <name> --fire-and-forget"`
- `CLAUDE_PROJECT_DIR` env var is set by the hook runner (Windows path)
- Project name is `path.basename(CLAUDE_PROJECT_DIR)`, sanitized
3. **`openclaw-checkin.py`** resolves project name from `--project` flag (or fallback: `basename(CLAUDE_PROJECT_DIR)`)
4. **`_update_tracker()`**: Reads `tracker.json`, finds the first `active` tab whose `project_name` matches (case-insensitive substring), appends a checkin entry, updates `last_checkin`. If `status == "done"`, marks tab `completed` with `completed_at` and `summary`. Writes atomically (`.tmp` → `os.replace()`). Wrapped in `try/except` — never raises.
5. **`_log_comms()`**: Appends a JSONL entry to `~/.openclaw/comms/claude-code.jsonl` (ts, dir, type, message, result, latency_ms)
6. **`send_to_openclaw()`**: POSTs to the OpenClaw chat API with `fire_and_forget=True` (5s timeout). Timeouts are expected and OK — tracker is already updated.
7. **claude-tab-monitor cron** (every 30 min): reads `tracker.json` via `manage-claude-code.py monitor`, checks `last_checkin` recency, detects stalled/dead/completed tabs, reports to main OpenClaw session.

---

## The Bug (Fixed 2026-04-27)

### Root Cause

Before the fix, `openclaw-checkin.py` only:
- Wrote to the comms log
- POSTed to the OpenClaw chat API (which always timed out at 5s — LLM round-trip is slow)

It **never wrote to `tracker.json`**.

`manage-claude-code.py` and the monitor cron **only read `tracker.json`** — they never parsed
the comms log or chat API responses.

Result: 32 checkins in `comms/claude-code.jsonl`, all with `"result": "timeout"`. Zero updates
to `tracker.json`. `last_checkin` was `null` for every active tab. The two sides were completely
disconnected.

### Fix Applied

1. **Added `_update_tracker(status, detail, project)`** to `openclaw-checkin.py`:
- Called from `main()` **before** the OpenClaw API POST (tracker always updated, even on API timeout)
- Finds matching tab by `project_name` (case-insensitive substring match, skips non-active tabs)
- Appends to `checkins[]`, updates `last_checkin`, optionally marks `completed`
- Atomic write (`.tmp` → `os.replace()`) — safe on POSIX/WSL
- Wrapped in `try/except` — never breaks the checkin flow

2. **Added `--fire-and-forget` to argparse** (hidden, accepted but ignored):
- `openclaw-checkin.js` was passing `--fire-and-forget` which caused `argparse` errors
- Added as a no-op flag for backwards compatibility

3. **Synced both script copies**: context-reset `scripts/` → `.claude/scripts/`

---

## Testing

```bash
# Simulate a checkin from a project that has an active tab in tracker.json
CLAUDE_PROJECT_DIR=/home/ubu/.openclaw/workspace \
python3 /mnt/c/Users/joelg/Documents/ProjectsCL1/_grobomo/context-reset/scripts/openclaw-checkin.py \
progress "test checkin" --quiet

# Verify tracker.json was updated:
jq '.tabs[] | select(.project_name == "workspace") | {last_checkin, checkins: (.checkins | length)}' \
/home/ubu/.openclaw/workspace/scripts/claude-tabs/tracker.json

# Expected: last_checkin is a recent ISO timestamp, checkins count > 0

# Test --fire-and-forget compat flag (accepted, no error):
CLAUDE_PROJECT_DIR=/home/ubu/.openclaw/workspace \
python3 /mnt/c/Users/joelg/Documents/ProjectsCL1/_grobomo/context-reset/scripts/openclaw-checkin.py \
--status done --detail "test done" --project workspace --fire-and-forget
# Expected: tab status changes to "completed" in tracker.json
```

---

## Notes

- The `--quiet` flag suppresses stdout/stderr output but does **not** suppress tracker writes
- The `--wait` flag waits up to 120s for the OpenClaw API reply; default is fire-and-forget (5s)
- Tracker writes succeed even when the OpenClaw API is completely unreachable
- Only **active** tabs are updated by checkins (completed/archived tabs are skipped)
- The comms log is append-only — it's an audit trail, not the source of truth for tab state
Loading
Loading