Skip to content
Merged
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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ All notable changes to the claude-plugins project will be documented in this fil

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Entries are listed newest-first; each plugin section is treated as released when merged to `main`.

### vibe v1.0.59

#### Added
- `codex-fleet-monitor` skill: a background event watcher for a fleet of Codex App Server threads (local and one ssh host) plus the pull requests they produce. It exits with a diff on the first change and flags IDLE, STALLED, FAILED, merge conflicts, review and comment counts, check changes and new PRs. Liveness is read from each thread's last turn and item count rather than the turn id alone, so a turn left open by a silent Codex usage limit is reported as STALLED. It needs only the vibe plugin and the `codex` CLI: `liveness.mjs` is a single self-contained file that talks to the managed App Server through `codex app-server proxy`, and `sessions.txt` lists sessions as `local|remote <label> <threadId>` (the two-column `<name>` form that reads a `<name>.json` state file with a `threadId` still works). The skill description tells agents to reach for it whenever two or more Codex or vibe sessions are running.
- `codex-fleet-monitor` prints `codex-usage <local|remote> <band>% <state> resets=<n>` for this machine and the ssh host from `account/rateLimits/read`, where the state is `ok`, `LIMIT`, `LIMIT-SPENDING-CREDITS` (the limit is reached and turns are consuming paid credits) or `LIMIT-STOPPED`. `ratelimit.mjs` reuses the App Server client exported by `liveness.mjs` and is bounded by the same in-process timeout; both files are copied to `<REMOTE_STATE_DIR>/.fleet-monitor/` on the remote host. The skill tells agents to warn the user when the band rises fast and to stop sending turns on `LIMIT-SPENDING-CREDITS` unless the user approves paid credits.
- Optional `REQUESTS_DIR` and `REMOTE_REQUESTS_DIR` print `requests <where> <count> <newest file>`, so the watcher wakes when a Codex session drops a delegated-work request for a Claude worker.
- `codex-fleet-monitor`'s `watch.sh` compares each run against the previous run's `last-snapshot.txt`, so a change between a watcher exit and its re-arm (such as a session switching to paid credits) is still reported. PR check counts are reported only once settled: while `gh` lists no checks or any check is pending, the line keeps the last settled counts with a ` (running)` suffix instead of flapping on every CI run.

### code-review v3.11.0

#### Changed
Expand Down
2 changes: 1 addition & 1 deletion plugins/vibe/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "vibe",
"description": "Vibe sessions, handoff, seed refresh, and ClosedLoop ticket workflows",
"version": "1.0.58",
"version": "1.0.59",
"dependencies": ["closedloop-core"],
"author": {"name": "ClosedLoop", "email": "support@closedloop.ai"}
}
2 changes: 1 addition & 1 deletion plugins/vibe/.codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "vibe",
"version": "1.0.58",
"version": "1.0.59",
"description": "Vibe sessions, handoff, seed refresh, and ClosedLoop ticket workflows",
"author": {"name": "ClosedLoop", "email": "support@closedloop.ai"},
"homepage": "https://github.com/closedloop-ai/claude-plugins",
Expand Down
15 changes: 15 additions & 0 deletions plugins/vibe/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,21 @@ through the queue, and mark the ticket Done. The fix worker runs the full web
and Desktop screen walk itself, on a Mac through `pnpm vibe walk-linux`, and
its counts and result file go into the PR's Test plan and the ticket's loop.

### `codex-fleet-monitor`

Background event watcher for a fleet of long-running Codex App Server threads
(local and one ssh host) plus the pull requests they produce. One command polls
every watched thread and PR and exits with a diff on the first change: a session
going idle, stalling, or failing, a merge conflict, new review or comment
counts, changed checks, or a new PR. Liveness is read from the thread's last
turn and item count, so a turn left open by a silent Codex usage limit shows as
`STALLED` instead of running forever. It also prints a Codex usage line for each
machine (used-percent band, and whether a reached limit has started spending
paid credits), check counts only once they settle, and optionally the
delegated-work request files sessions drop for Claude workers. A change made
between a watcher exit and its re-arm is still reported. It needs only this
plugin and the `codex` CLI; sessions are listed by thread id.

### ClosedLoop Ticket Skills

The plugin bundles the ClosedLoop ticket automation skill pack: `cl-policy`, `cl-analyze`, `cl-find-related-tickets`, `cl-split`, `cl-work-report`, `cl-sweep`, and `cl-execute`. Together they cover policy-aware ticket analysis, related-ticket discovery, safe one-level split workflows, private project work reports, sweep coordination, and end-to-end ticket execution. The imported sweep scripts use the native managed Codex App Server through bundled helpers rather than depending on a separate `app-server-orchestrator` skill. Runtime access still depends on the operator's normal ClosedLoop, GitHub, `workflow-memory`, and `closedloop-graph` MCP/API configuration.
Expand Down
82 changes: 82 additions & 0 deletions plugins/vibe/skills/codex-fleet-monitor/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
---
name: codex-fleet-monitor
description: 'Use PROACTIVELY whenever you start or are already running two or more Codex or vibe sessions, or are watching PRs on their behalf, and need to be told when anything needs attention. Replaces hand-rolled polling loops: a Codex turn that hits a usage limit stays "active" and silent, so a watcher that checks only for an open turn misses it. This reads each thread''s progress and flags IDLE, STALLED, FAILED(LIMIT), new PR comments or reviews, settled check changes, merge conflicts, and new PRs. It also prints a Codex usage line per machine (used-percent band, and whether a reached limit is spending paid credits) and, when configured, the delegated-work request files sessions drop for Claude workers.'
---

# codex-fleet-monitor

One background command that polls every watched Codex thread and PR, and exits with a diff the moment any line changes. You read the diff, act, then re-arm it. It costs no model tokens while waiting.

## Why liveness is read from the thread, not the turn id

A Codex turn that hits a usage limit stays open and simply stops producing items. Checking only "is a turn active" reports it as running forever. This monitor reads each thread's last turn (status, error, item count) and flags a turn whose item count has not grown for `STALL_SECONDS` as `STALLED`.

## Requirements

- Each Codex session is identified by its thread id. `scripts/liveness.mjs` is self-contained (Node built-ins only): it starts the managed App Server with `codex app-server daemon start`, connects through `codex app-server proxy`, and calls `thread/read`. `scripts/ratelimit.mjs` reuses its client to call `account/rateLimits/read`. Nothing outside the vibe plugin and the `codex` CLI is needed.
- `codex`, `node`, `jq`, `gh` (authenticated) on this machine; `codex` and `node` on the remote host, reachable through `REMOTE_SHELL`.

## Set up a work dir

Pick a scratch dir (one per orchestration) and create:

`config.env`

```sh
LOCAL_STATE_DIR=/path/to/local/state/files # only for two-column sessions
REMOTE_HOST=user@host # omit when there are no remote sessions
REMOTE_STATE_DIR=/path/on/remote # remote scratch dir; also holds state files for two-column sessions
REMOTE_SHELL="zsh -lic" # login shell so node/codex are on PATH (default)
STALL_SECONDS=720 # no new thread items for this long means STALLED (default 12m)
WATCH_NEW_PR_REPOS="owner/repo" # optional: list open PRs here to spot new ones
PR_SEARCH="head:feature/" # optional: search filter for that list
BOT_LOGIN_REGEX="vercel|codex|github-actions|copilot|bot" # authors not counted as human feedback
# MY_GH_LOGIN=me # your own comments are not counted; defaults to `gh api user`
REQUESTS_DIR=/path/to/requests # optional: local dir where sessions drop delegated-work request files
REMOTE_REQUESTS_DIR=/path/on/remote/requests # optional: the same on REMOTE_HOST
```

`sessions.txt`: one line per Codex session, in either form:

- `local|remote <label> <threadId>`: the thread id directly.
- `local|remote <name>`: reads `<stateDir>/<name>.json`, which must contain a `threadId` field (`LOCAL_STATE_DIR` or `REMOTE_STATE_DIR`). Use this when another tool already writes per-session state files.

The label is what appears in the output lines.

`prs.txt`: one line per PR to watch, `owner/repo <number>`. Add a PR when it opens, remove it when it merges.

## Run

```sh
scripts/watch.sh <workDir> [intervalSeconds]
```

Start it with `run_in_background`. You are notified when it exits. Read its output, handle every line, then start it again; it only watches while running. Editing `sessions.txt` or `prs.txt` takes effect on the next re-arm. `scripts/snapshot.sh <workDir>` prints the current state once.

## Reading the output

| Line | Meaning | Usual action |
|---|---|---|
| `session <where> <name> RUN` | turn in progress and producing items | none |
| `session ... IDLE` | turn completed; the session is waiting for you | read its last message, give it the next turn |
| `session ... STALLED(...)` | turn open but silent | check for a usage limit or a hung command; tell the user if it needs them |
| `session ... FAILED(LIMIT)` | turn errored on usage or rate limit | tell the user right away; a reset or second account is theirs to do |
| `session ... FAILED(ERROR)` / `STOPPED(...)` / `READFAIL` | turn errored, was interrupted, or the thread or state file is unreadable | inspect the thread; restart or resume |
| `session remote <host> UNREACHABLE` | ssh failed | check the host |
| `codex-usage <local\|remote> <band>% ok resets=<n>` | Codex account usage on that machine, used percent floored to 10; `resets` is the count of available limit resets | none; if the band rises fast, warn the user with the rate |
| `codex-usage ... LIMIT` / `LIMIT-STOPPED` | usage limit reached; `LIMIT` means ordinary usage is still allowed, `LIMIT-STOPPED` means sessions cannot run | tell the user right away; a reset or second account is theirs to do |
| `codex-usage ... LIMIT-SPENDING-CREDITS` | limit reached and turns are now consuming paid credits | tell the user at once and stop sending turns unless they approve paid credits |
| `codex-usage ... unreadable READFAIL` | the App Server or remote host could not be read | check `codex` on that machine |
| `requests <local\|remote> <count> <newest file>` / `requests ... UNREADABLE` | a session dropped (or you removed) a delegated-work request file | hand the new request to a Claude worker, then steer its result back to the session that asked |
| `open-prs <repo> <numbers>` | open PR list changed | add the new PR to `prs.txt` and start its review |
| `pr ... <state> <head> <reviewDecision> <mergeable> comments= reviews= inline= checks=` | any field changed | new head: re-review; count up: read the feedback; `CONFLICTING`: tell the owning session to merge the base branch; checks: act on failures. Check counts change only once settled; while any check is pending the last settled counts show with ` (running)` |

Surface everything that needs a human to the user immediately: limits, paid-credit spending, stalls, failures, and new human review comments.

## Notes

- `mergeable` keeps its last known value while GitHub reports `UNKNOWN`, so it does not flap.
- Each run compares against `<workDir>/last-snapshot.txt` from the previous run, so a change between a watcher exit and its re-arm is still reported. Delete the file to start from a fresh base.
- Item history lives in `<workDir>/item-history.txt`; delete it to reset stall timers. Settled check counts live in `<workDir>/checks-*.txt`.
- On the remote host both scripts are copied to `<REMOTE_STATE_DIR>/.fleet-monitor/` on every snapshot.
- Don't run two copies on the same work dir.
4 changes: 4 additions & 0 deletions plugins/vibe/skills/codex-fleet-monitor/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
interface:
display_name: "Codex Fleet Monitor"
short_description: "Get woken when a Codex session or PR needs attention"
default_prompt: "Use $codex-fleet-monitor to watch my Codex sessions and their pull requests."
16 changes: 16 additions & 0 deletions plugins/vibe/skills/codex-fleet-monitor/scripts/classify.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/bin/zsh
# stdin: "<where> <stateName> <turnStatus> <items> <flag>". $1: item history file. $2: stall seconds.
# A running turn whose item count has not grown for $2 seconds is STALLED (a Codex usage limit
# leaves the turn open and silent, so "turn is open" alone is not liveness).
H=$1; STALL=${2:-720}; touch $H; now=$(date +%s)
while read where n st items flag; do
prev=$(grep "^$where:$n " $H | awk '{print $2, $3}'); pi=${prev% *}; pt=${prev#* }
if [ -z "$prev" ] || [ "$pi" != "$items" ]; then pt=$now; fi
grep -v "^$where:$n " $H > $H.tmp; echo "$where:$n $items $pt" >> $H.tmp; mv $H.tmp $H
if [ "$flag" != "-" ]; then s="FAILED($flag)"
elif [ "$st" = inProgress ] && [ $((now - pt)) -ge $STALL ]; then s="STALLED(no progress $((STALL / 60))m+)"
elif [ "$st" = inProgress ]; then s=RUN
elif [ "$st" = completed ]; then s=IDLE
else s="STOPPED($st)"; fi
echo "session $where $n $s"
done
Loading
Loading