Skip to content

Commit 97a0d19

Browse files
authored
docs(skills): curl-to-REST fallback for overview maintenance in broken-CLI sandboxes (#2164)
The weekly SANA sandbox routes egress through a mandatory TLS-intercepting proxy that the compiled Bun CLI binary can't traverse (a Bun proxy/HTTP-2 bug, not a config issue — see #2163). Every admin CLI command in these skills wraps an existing REST route, so document a curl-to-REST fallback: - regenerating-overviews: new "Running without the CLI" section covering overview inputs (GET), generate, overview update (POST), and the plan manifest, plus the --max-content-chars client-side-clipping nuance. - maintaining-orgs: pointer to the fallback; notes admin source fetch has no curl equivalent yet (skip fetch, regen from indexed data in-sandbox). Refs #2163
1 parent c2bd4f3 commit 97a0d19

2 files changed

Lines changed: 65 additions & 0 deletions

File tree

.claude/skills/maintaining-orgs/SKILL.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ releases admin overview inputs <slug> --json
3939
releases admin overview update <slug> --content-file /tmp/<slug>-overview.md
4040
```
4141

42+
> **Sandbox without a working CLI?** If the compiled `releases` binary can't reach the API through a TLS-intercepting egress proxy (the weekly SANA sandbox), the overview steps have a curl-to-REST fallback — see `regenerating-overviews`_Running without the CLI (curl fallback)_, including the plan manifest (`GET /v1/admin/overviews?format=plan`) that drives target selection below. **Step 1 (`admin source fetch`) has no curl fallback yet** — it dispatches a managed update run and polls, not a single REST call. In a broken-CLI sandbox, skip the fetch and regenerate from current indexed data (surface that the fetch was skipped); a proper fetch-trigger fallback is tracked in [#2163](https://github.com/buildinternet/releases/issues/2163).
43+
4244
For orgs with `scrape` or `agent` sources, skim the playbook before fetching — it documents source-specific quirks that may affect `--max` or require crawl flags:
4345

4446
```bash

.claude/skills/regenerating-overviews/SKILL.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ No managed-agent task exists for this today. You — the Claude Code instance re
2525

2626
Three steps. Each step is a single CLI invocation; no other tools needed.
2727

28+
> **Sandbox without a working CLI?** In an environment where the compiled `releases` binary can't reach the API through a TLS-intercepting egress proxy (e.g. the weekly SANA sandbox), skip the CLI and hit the REST routes directly — see [Running without the CLI (curl fallback)](#running-without-the-cli-curl-fallback). Same three steps, same contracts.
29+
2830
### 1. Fetch inputs
2931

3032
```bash
@@ -208,6 +210,67 @@ Optional flags:
208210

209211
The CLI POSTs to `/v1/orgs/:slug/overview` (dumb upsert). Last-write-wins on conflict for both the body and the citations.
210212

213+
## Running without the CLI (curl fallback)
214+
215+
Use this when the compiled `releases` binary can't complete requests — the known case is the weekly SANA sandbox, whose mandatory TLS-intercepting egress proxy the compiled Bun binary can't get through. This was chased to ground in [releases#2163](https://github.com/buildinternet/releases/issues/2163): it is **not** a configuration problem. The failure (`socket connection was closed unexpectedly`) persists across every lever — the correct ambient `HTTPS_PROXY` (the sandbox's proxy port rotates per shell, but Bun reads it correctly each time), all of `NODE_EXTRA_CA_CERTS` / `SSL_CERT_FILE` / `NODE_USE_SYSTEM_CA=1`, and even bypassing the proxy via `NO_PROXY` (which just hits the sandbox's blocked direct egress). Meanwhile `curl` through the same proxy works cleanly (CONNECT + TLS 1.3, cert verified against `/root/.ccr/ca-bundle.crt`), so the fault is inside the compiled Bun binary's proxy/TLS networking layer, not anything an env var fixes. Every CLI command in this skill is a thin wrapper over a REST route, so call the route directly with `curl` — the contracts are identical.
216+
217+
Use the admin token already in the environment — `$RELEASES_API_KEY`, the same credential the CLI uses. Do **not** read `.env` or fetch secrets of any kind (the [Don't Confabulate](#dont-confabulate-around-tool-failures) rule still holds). A `relu_` user key won't work here: these are admin routes and the write needs `write` scope, which user keys can't hold — you need the root `RELEASES_API_KEY` or a `relk_` admin/write token.
218+
219+
Setup:
220+
221+
```bash
222+
API="${RELEASES_API_URL:-https://api.releases.sh}"
223+
AUTH="Authorization: Bearer ${RELEASES_API_KEY}"
224+
```
225+
226+
### Step 1 — inputs (replaces `releases admin overview inputs`)
227+
228+
`GET /v1/orgs/:slug/overview/inputs?window=30` — admin-only, Bearer auth. The `--max-content-chars` flag is **client-side clipping in the CLI, not a query param**, so the wire payload is never capped. Write the full response to a file (files have no stdout cap), then read a content-clipped view — this preserves the "never generate from a truncated slice" rule:
229+
230+
```bash
231+
curl -fsS -H "$AUTH" "$API/v1/orgs/$SLUG/overview/inputs?window=30" -o /tmp/$SLUG-inputs.json
232+
# clip each release body to 1000 chars (what step 2 truncates to anyway) for reading:
233+
jq '.selected |= map(.content |= .[0:1000])' /tmp/$SLUG-inputs.json > /tmp/$SLUG-inputs-clipped.json
234+
```
235+
236+
Read `/tmp/$SLUG-inputs-clipped.json`. It carries `org`, `sources`, `existingContent`, `selected`, `totalAvailable`, `windowDays` — same shape as the CLI's `--json`. Same stop rules apply: `selected` empty (`totalAvailable: 0`) → stop, don't generate. Clipping only trims each `content` string, never drops entries, so `selected.length` is unchanged between the raw and clipped files — if the counts differ, something else truncated and you should re-fetch. Lightweight pre-flight (like the CLI's default non-`--json` check): add `&check=true`.
237+
238+
### Step 2 — generate
239+
240+
Unchanged — this is the parent harness's Anthropic call described above. Build the `search_result` blocks from `/tmp/$SLUG-inputs-clipped.json`.
241+
242+
### Step 3 — write (replaces `releases admin overview update`)
243+
244+
`POST /v1/orgs/:slug/overview` — admin-gated. Assemble the JSON body with `jq` (so the markdown body is escaped safely), then POST it:
245+
246+
```bash
247+
jq -n \
248+
--rawfile content /tmp/$SLUG-overview.md \
249+
--slurpfile citations /tmp/$SLUG-overview-citations.json \
250+
--argjson releaseCount "$TOTAL_AVAILABLE" \
251+
--arg lastAt "$LAST_CONTRIBUTING_AT" \
252+
'{content:$content, releaseCount:$releaseCount, citations:$citations[0]}
253+
+ (if $lastAt=="" then {} else {lastContributingReleaseAt:$lastAt} end)' \
254+
> /tmp/$SLUG-overview-body.json
255+
256+
curl -fsS -X POST -H "$AUTH" -H "Content-Type: application/json" \
257+
--data @/tmp/$SLUG-overview-body.json \
258+
"$API/v1/orgs/$SLUG/overview"
259+
```
260+
261+
- `$TOTAL_AVAILABLE``totalAvailable` from step 1 (the CLI defaults `--release-count` to this).
262+
- `$LAST_CONTRIBUTING_AT` — the first selected release's `publishedAt`; leave the var empty to omit it (the server defaults it).
263+
- **No citations?** Drop the `--slurpfile`/`citations` key entirely. Omitting `citations` clears any prior citations on the page (replace-all semantics) — matching the CLI.
264+
- Body field names are exactly `content`, `releaseCount`, `lastContributingReleaseAt`, `citations`; the citations array is `{ startIndex, endIndex, sourceUrl, title?, citedText }` per step 2.
265+
266+
`curl -fsS` exits non-zero on any non-2xx — treat that as a hard stop and surface it, exactly like a CLI error. Never fall back to regenerating from stale inputs.
267+
268+
### Cross-org target selection (plan manifest)
269+
270+
`maintaining-orgs` picks targets from `releases admin overview plan`, which is `GET /v1/admin/overviews?format=plan` — the freshness fields (`overviewUpdatedAt`, `releasesSinceOverview`, `recentReleaseCount`, `needsFetch`) come back directly, no CLI needed.
271+
272+
> The remote MCP server does **not** expose these admin routes as tools (it's a read-only public catalog surface). So in a broken-CLI sandbox, curl-to-REST — not MCP — is the fallback for all three steps. Exposing them as write-scoped MCP tools is a separate, deliberate decision tracked in [#2163](https://github.com/buildinternet/releases/issues/2163).
273+
211274
## Failure Modes to Watch For
212275

213276
- **Empty selection** → stop and report no-op. Don't generate.

0 commit comments

Comments
 (0)