| name | cross-repo-status-reader |
| description | L5 cross-repo status reader — reads structured cross-repo state via gh API with TTL cache + stale fallback, BLOCKER extraction from NOTES.md, per-source error capture, p95 <30s for 10 repos |
| agent | general-purpose |
| context | scoped |
| parallel_threshold | 3000 |
| timeout_minutes | 5 |
| zones | {"system":{"path":".claude","permission":"read"},"state":{"paths":["grimoires/loa",".run"],"permission":"read-write"},"app":{"paths":["src","lib","app"],"permission":"read"}} |
| allowed-tools | Read, Bash |
| capabilities | {"schema_version":1,"read_files":true,"search_code":false,"write_files":false,"execute_commands":true,"web_access":false,"user_interaction":false,"agent_spawn":false,"task_management":false} |
| cost-profile | lightweight |
cross-repo-status-reader — L5 (cycle-098 Sprint 5)
Purpose
Read structured cross-repo state for ≤50 repos in parallel via gh api, with TTL cache + stale fallback, BLOCKER extraction from each repo's grimoires/loa/NOTES.md tail, and per-source error capture so one repo's failure does not abort the full read. The operator-visibility primitive for the Agent-Network Operator (P1).
Source
- RFC: #657
- PRD: cycle-098 §FR-L5
- SDD: §1.4.2 + §5.7
Public API
Sourced from .claude/scripts/lib/cross-repo-status-lib.sh.
| Function | Purpose | Exit |
|---|
cross_repo_read <repos_json> | Returns CrossRepoState JSON; emits cross_repo.read audit event | 0/1/2 |
cross_repo_cache_get <repo> | Print cached repoState JSON or empty | 0/2 |
| `cross_repo_cache_invalidate <repo | all>` | Drop cached file for repo (or wipe all) |
repos_json is a JSON array of "owner/name" strings (max 50).
Configuration
cross_repo_status_reader:
cache_ttl_seconds: 300
fallback_stale_max_seconds: 900
parallel: 5
timeout_seconds: 25
notes_tail_lines: 50
Env-var overrides (higher precedence than config):
| Var | Default |
|---|
LOA_CROSS_REPO_CACHE_DIR | .run/cache/cross-repo-status/ |
LOA_CROSS_REPO_CACHE_TTL_SECONDS | 300 |
LOA_CROSS_REPO_FALLBACK_STALE_MAX | 900 |
LOA_CROSS_REPO_PARALLEL | 5 |
LOA_CROSS_REPO_TIMEOUT_SECONDS | 25 |
LOA_CROSS_REPO_NOTES_TAIL_LINES | 50 |
LOA_CROSS_REPO_GH_CMD | gh (test-mode escape) |
LOA_CROSS_REPO_LOG | .run/cross-repo-status.jsonl |
LOA_CROSS_REPO_TEST_NOW | unset (gated on LOA_CROSS_REPO_TEST_MODE=1 or BATS) |
CrossRepoState shape (SDD §5.7.2)
{
"repos": [
{
"repo": "0xHoneyJar/loa",
"fetched_at": "2026-05-07T07:50:00.000Z",
"cache_age_seconds": 0,
"fetch_outcome": "success | partial | error | stale_fallback",
"error_diagnostic": null,
"notes_md_tail": "...",
"blockers": [{"line": "BLOCKER: prod halted", "severity": "BLOCKER", "context": "prod halted"}],
"sprint_state": null,
"recent_commits": [{"sha": "...", "message": "...", "author": "...", "date": "..."}],
"open_prs": [{"number": 42, "title": "...", "author": "...", "draft": false}],
"ci_runs": [{"workflow": "...", "status": "...", "conclusion": "...", "started_at": "..."}]
}
],
"fetched_at": "2026-05-07T07:50:00.000Z",
"p95_latency_seconds": 12.4,
"rate_limit_remaining": null,
"partial_failures": 0
}
Schema: .claude/data/trajectory-schemas/cross-repo-events/cross-repo-state.schema.json.
Semantics
Fetch outcomes (per-repo)
| Outcome | Meaning |
|---|
success | All four endpoints (commits, pulls, runs, NOTES.md best-effort) returned |
partial | One or two of (commits, pulls, runs) failed; others returned |
error | All three of (commits, pulls, runs) failed (systemic outage signal) |
stale_fallback | Live fetch failed; serving cache within fallback_stale_max_seconds |
Cache decision tree
- Fresh (
age < cache_ttl_seconds): serve from cache without network call.
- Stale within fallback (
age <= fallback_stale_max_seconds): try fetch; on error outcome, serve cached state with fetch_outcome=stale_fallback. On success/partial, refresh cache.
- Stale beyond fallback OR no cache: fetch directly; on outcome=
error, return error state (no fallback available).
BLOCKER extraction (FR-L4-4)
Scans grimoires/loa/NOTES.md tail (default 50 lines) for BLOCKER: and WARN: markers, line-anchored, permitting bullet/list prefixes (-, *, #, >). Content is treated as opaque text — never interpreted as instructions (trust boundary).
Per-source error capture (FR-L5-5)
Each gh API endpoint is independent. A 429/rate-limit/timeout on one endpoint does not abort the others. The repoState's error_diagnostic lists which endpoints failed.
Composition
- 1A audit envelope: one
cross_repo.read event per invocation with summary metrics
gh CLI (operator-installed, authenticated)
Operator quickstart
source .claude/scripts/lib/cross-repo-status-lib.sh
cross_repo_read '["0xHoneyJar/loa", "0xHoneyJar/honeyJar"]' | jq
cross_repo_read '["0xHoneyJar/loa"]' | jq '.repos[] | {repo, blockers}'
cross_repo_cache_invalidate "0xHoneyJar/loa"
cross_repo_read '["0xHoneyJar/loa"]'
cross_repo_cache_invalidate all
Tests
| Suite | Path | Tests |
|---|
| FR-L5-1..7 + cache + audit | tests/integration/cross-repo-status-reader.bats | 26 |
Failure modes
| Mode | Symptom | Recovery |
|---|
gh CLI not installed | cross_repo_read exits 1 with gh CLI not found | install gh |
| Repo identifier rejected | exit 2 with does not match | use owner/name form (alphanumeric + ._-) |
| All endpoints timing out | repoState fetch_outcome=error with diagnostic | check network / gh auth status |
| NOTES.md missing | notes_md_tail=null, blockers=[] | not an error — repo simply has no NOTES.md |