Skip to content

Commit 799b244

Browse files
longfinclaude
andauthored
docs(skills): add vicoop-bridge-logs operator skill (#391)
A read-only Claude Code / Codex skill for inspecting a deployed vicoop-bridge *server*'s runtime logs on Fly.io. The server emits a structured JSON event stream via logEvent() (packages/server/src/log.ts); this skill captures a recent window with `fly logs` and summarizes it (event tallies, rejections by reason, per-backend activity, KST times). - Event reference table derived from the logEvent( call sites in packages/server/src (16 events incl. agent_request, *_rejected reasons, task lifecycle, client connect/disconnect, maintenance/error events), with a one-liner to re-derive it so the table stays in sync. - App name parameterized via $BRIDGE_APP (defaults to the conventional vicoop-bridge-server) so it isn't tied to one deployment. - Cross-platform capture (Linux `timeout` vs macOS background-kill); bounded `fly logs` so it never blocks the session. - Privacy note: captured windows carry email/principal identifiers. Ships under skills/ next to fork-into-container, with the same Install section pattern. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 0ca8d6b commit 799b244

1 file changed

Lines changed: 206 additions & 0 deletions

File tree

skills/vicoop-bridge-logs/SKILL.md

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
---
2+
name: vicoop-bridge-logs
3+
description: Inspect the runtime logs of a deployed vicoop-bridge server on Fly.io. Captures the server's stdout and summarizes its structured JSON event stream — agent requests, task completions/failures, rejections (by reason), client connect/disconnect, and backend activity — converting timestamps to KST. Complements admin-API metrics by exposing live runtime events. Use when the user says "check bridge logs", "브리지 로그 봐줘", "who got rejected / 누가 거부됐는지", "is backend X getting traffic / 백엔드 가용성", "task 완료·실패 추적", "client connect 추적", "bridge runtime logs / 브리지 런타임 로그".
4+
allowed-tools: Bash
5+
---
6+
7+
# vicoop-bridge-logs
8+
9+
Read and summarize the **runtime logs** of a deployed `vicoop-bridge` **server**
10+
(`packages/server`, the gateway deployed via `fly deploy`). Every server log line
11+
is a single JSON object emitted by `logEvent()`
12+
(`packages/server/src/log.ts`) — `{ ...fields, event, ts }` — so the logs are a
13+
structured event stream, not free text. This skill captures a recent window with
14+
`fly logs` and summarizes it.
15+
16+
> **Scope.** This is read-only log inspection of the **server** deployment. It does
17+
> not SSH, mutate the app, or touch the database. App lifecycle (status, restarts,
18+
> Postgres) is generic Fly.io work — use your Fly ops tooling for that.
19+
20+
## Install
21+
22+
This repo ships the skill source under `skills/vicoop-bridge-logs/`. Drop it into
23+
your agent's skills tree on the host:
24+
25+
**Claude Code** (user-wide):
26+
27+
```bash
28+
DEST=~/.claude/skills/vicoop-bridge-logs
29+
mkdir -p "$DEST"
30+
cp -R skills/vicoop-bridge-logs/. "$DEST/"
31+
```
32+
33+
Project-scoped variant: replace `~/.claude` with `.claude` inside the repo you're
34+
working in. **Codex**: replace `~/.claude` with `~/.codex`.
35+
36+
## Prerequisites
37+
38+
- `flyctl` authenticated, with access to the org that owns your bridge server app.
39+
- `jq` for parsing the JSON event stream.
40+
- The **Fly app name** of your server deployment. Set it once per session; every
41+
snippet below honors it and falls back to the conventional `vicoop-bridge-server`:
42+
43+
```bash
44+
export BRIDGE_APP="${BRIDGE_APP:-vicoop-bridge-server}"
45+
```
46+
47+
If you don't know it, `fly apps list` (filter for your bridge) or check the app's
48+
`fly.toml`.
49+
50+
## Core capture pattern
51+
52+
`fly logs --no-tail` prints the recent buffer then **blocks waiting for new lines**
53+
— it does not exit on its own. Bound it:
54+
55+
```bash
56+
# Linux (coreutils `timeout` available):
57+
timeout 10 fly logs -a "$BRIDGE_APP" --no-tail 2>/dev/null
58+
59+
# macOS / no `timeout` — run in background and kill after a few seconds:
60+
( fly logs -a "$BRIDGE_APP" --no-tail 2>/dev/null & p=$!; sleep 10; kill "$p" 2>/dev/null )
61+
```
62+
63+
flyctl prints lines as `<ts> app[<machine>] <region> [info]{...json...}` with ANSI
64+
color. Extract the JSON payload with `grep -oE '\{.*\}'` (this also drops the ANSI
65+
noise) and parse with `jq`:
66+
67+
```bash
68+
( fly logs -a "$BRIDGE_APP" --no-tail 2>/dev/null & p=$!; sleep 10; kill "$p" 2>/dev/null ) \
69+
| grep -oE '\{.*\}' | jq -c 'select(.event)'
70+
```
71+
72+
The flyctl stderr line `Warning: Metrics token unavailable…` is harmless — that's
73+
why the snippets drop stderr (`2>/dev/null`).
74+
75+
## Event reference
76+
77+
Derived from `logEvent(` call sites in `packages/server/src`. Every event carries
78+
`event` and `ts`; the fields below are the notable extras. Optional fields are
79+
omitted when absent.
80+
81+
### Request lifecycle
82+
83+
| `event` | source | key fields | meaning |
84+
|--------------------------|-----------------------|------------|---------|
85+
| `agent_request` | `http.tsx` | `agentId`, `backend`, `hasAuth`, `ownerEmail?`, `principalId?`, `callerEmail?` | inbound `POST /agents/:id` accepted (the common "traffic" event) |
86+
| `agent_request_rejected` | `agent-auth.ts` | `agentId`, `reason`, `rejectionId`, `principalId?` | inbound request refused — **the key ops signal** (see reasons below) |
87+
| `task_completed` | `ws.ts` | `agentId`, `backend`, `taskId`, `contextId`, `state`, `principalId?` | client reported the task terminal; `state` is usually `completed` |
88+
| `task_failed_by_client` | `ws.ts` | `agentId`, `backend`, `taskId`, `contextId`, `errorCode`, `errorMessage`, `principalId?` | the connected agent failed the task |
89+
| `task_unreachable` | `executor.ts` | `agentId`, `taskId`, `contextId`, `principalId?` | no live client to deliver the task to (agent dropped mid-flight) |
90+
91+
**`agent_request_rejected` reasons** (from `agent-auth.ts`, roughly in check order):
92+
`missing_bearer`, `bad_token_prefix`, `invalid_token`, `invalid_siwe_bearer`,
93+
`owner_session_on_caller_route`, `caller_not_authorized` (authenticated but not
94+
permitted for that agent), `agent_not_connected`, `agent_unavailable` (no backend
95+
registered for the agent id). The first six are auth failures; the last two mean
96+
the target agent isn't reachable.
97+
98+
### Client / agent connection
99+
100+
| `event` | source | key fields | meaning |
101+
|-----------------------|----------------|------------|---------|
102+
| `client_connected` | `ws.ts` | `agentId`, `clientId`, `ownerPrincipal`, `backend`, `email?` | an operator's agent attached over the outbound WebSocket |
103+
| `client_disconnected` | `ws.ts` | `agentId`, `clientId?`, `ownerPrincipal?`, `backend?`, `email?` | that agent detached |
104+
| `client_rejected` | `ws.ts` | `agentId`, `reason` | a WS handshake was refused (`agent id reserved`, `bad token`, `agent not allowed`, or a card/handshake-specific reason) |
105+
| `client_collision` | `registry.ts` | `agentId`, … | two clients claimed the same agent id |
106+
107+
### Maintenance & internal errors
108+
109+
| `event` | source | meaning |
110+
|----------------------------------|-------------------|---------|
111+
| `a2a_tasks_pruned` | `index.ts` | retention sweep deleted stale task contexts (`deleted`, `retentionDays`) |
112+
| `agent_lookup_failed` | `agent-auth.ts` | agent registry/db lookup failed for a request |
113+
| `task_persist_error` | `executor.ts` | best-effort task persistence failed (`taskId`, `error`) |
114+
| `admin_persist_error` | `admin.ts` | admin-side persistence failed |
115+
| `admin_api_error` | `http.tsx` | an admin API call errored |
116+
| `caller_last_used_touch_failed` | `auth/caller-token.ts` | best-effort `last_used_at` update failed (benign on read-only DB) |
117+
| `registry_agent_listener_error` | `registry.ts` | a registry listener threw |
118+
119+
> This table is generated from the source; if you add a `logEvent(` call, add a row
120+
> here in the same change. Re-derive the full list with:
121+
> `grep -rhoE "logEvent\(['\"][a-zA-Z0-9_]+" packages/server/src | grep -oE "[a-zA-Z0-9_]+$" | sort -u`
122+
123+
**Privacy.** Events may carry `ownerEmail` / `callerEmail` / `email` and
124+
`principalId` / `ownerPrincipal` (namespaced `apikey:…`, `eth:0x…`, `google:…`).
125+
Caller-controlled strings are bounded via `truncate()` before logging, but treat a
126+
captured window as containing operator/user identifiers — don't paste it into
127+
public channels unredacted.
128+
129+
## Subcommands
130+
131+
Parse the subcommand from the request; default to `summary`.
132+
133+
### summary (default)
134+
135+
Tally events over a recent window and report health, KST-localized.
136+
137+
```bash
138+
( fly logs -a "$BRIDGE_APP" --no-tail 2>/dev/null & p=$!; sleep 10; kill "$p" 2>/dev/null ) \
139+
| grep -oE '\{.*\}' \
140+
| jq -rc 'select(.event) | {event, reason, state, backend}' \
141+
| sort | uniq -c | sort -rn
142+
```
143+
144+
Report: counts per `event` (per `reason` for rejections, per `state` for tasks),
145+
which backends are active, last activity time (max `ts`) **converted to KST
146+
(UTC+9)**, and any error-class events present.
147+
148+
### rejections
149+
150+
The most common reason to read these logs.
151+
152+
```bash
153+
( fly logs -a "$BRIDGE_APP" --no-tail 2>/dev/null & p=$!; sleep 10; kill "$p" 2>/dev/null ) \
154+
| grep -oE '\{.*\}' \
155+
| jq -rc 'select(.event=="agent_request_rejected") | "\(.ts)\t\(.reason)\t\(.agentId)\t\(.principalId // "-")\t\(.rejectionId)"'
156+
```
157+
158+
Group by `reason` + `agentId`. `agent_unavailable` / `agent_not_connected` → the
159+
backend agent isn't attached (cross-check `client_connected` for that `agentId`).
160+
`caller_not_authorized` and the auth-failure reasons → report the offending
161+
`principalId`.
162+
163+
### backend \<name\>
164+
165+
Filter to one backend/agent (e.g. `claude`, a codex backend, `inline`).
166+
167+
```bash
168+
( fly logs -a "$BRIDGE_APP" --no-tail 2>/dev/null & p=$!; sleep 10; kill "$p" 2>/dev/null ) \
169+
| grep -oE '\{.*\}' \
170+
| jq -rc --arg b NAME 'select(.event and (.backend==$b or ((.agentId // "")|test($b)))) | "\(.ts)\t\(.event)\t\(.agentId)\t\(.state // .reason // "-")"'
171+
```
172+
173+
Replace `NAME`. Answers "is backend X getting traffic / completing tasks / being
174+
rejected?".
175+
176+
### tail [seconds]
177+
178+
Bounded live follow (default 15s) — same kill-guard so it never blocks:
179+
180+
```bash
181+
( fly logs -a "$BRIDGE_APP" 2>/dev/null & p=$!; sleep 15; kill "$p" 2>/dev/null )
182+
```
183+
184+
(Without `--no-tail` it follows new lines.) Use when watching a deploy or
185+
reproducing a live rejection.
186+
187+
### errors
188+
189+
Surface non-routine activity: failures, rejections, and internal `*_error` events.
190+
191+
```bash
192+
( fly logs -a "$BRIDGE_APP" --no-tail 2>/dev/null & p=$!; sleep 10; kill "$p" 2>/dev/null ) \
193+
| grep -oE '\{.*\}' \
194+
| jq -rc 'select(.event | test("rejected|failed|error|unreachable|collision")) | {ts, event, reason, errorCode, agentId}'
195+
```
196+
197+
## Tips
198+
199+
- Always convert `ts` (ISO-8601 UTC) to **KST (UTC+9)** in summaries.
200+
- The capture only holds the recent buffer; `fly logs` has no start-time flag on
201+
the CLI. For historical lookups use the Fly dashboard log search.
202+
- Empty output usually means the machine is stopped or idle, not an error — confirm
203+
with `fly status -a "$BRIDGE_APP"`.
204+
- A recurring non-JSON `MaxListenersExceededWarning` line may appear; it's a Node
205+
runtime warning surfaced on stdout, not a per-request failure. The JSON-only
206+
snippets above (`grep -oE '\{.*\}'`) skip it.

0 commit comments

Comments
 (0)