Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8161f15e4 | ||
|
|
7934367f15 | ||
|
|
d48e47adfe | ||
|
|
0c115ea714 | ||
|
|
060978209c | ||
|
|
9e7770ff43 | ||
|
|
f33c1fe36f | ||
|
|
6e93922e01 | ||
|
|
0e5995960a | ||
|
|
0354b1714d | ||
|
|
4454fe49ef |
@@ -24,3 +24,51 @@ run `/cctest-intro` / `/cctest-setup-sandbox` first.
|
||||
timers) from an ARM skill — cc-ci work goes through the cc-ci skills.
|
||||
- The submodule is **pinned**: don't commit into it from here; upstream ARM changes arrive via
|
||||
a deliberate submodule bump + `scripts/gen-cctest-skills.py` regeneration.
|
||||
|
||||
**Sandboxed vs non-sandboxed mode.** This skill needs only git + (optionally) the Gitea API —
|
||||
not the test server — so it can run either way. Probe, then follow that branch:
|
||||
|
||||
- **Sandboxed** (ARM env configured: `test-ssh/.testenv` with `GITEA_USERNAME`/`GITEA_PASSWORD`/
|
||||
`GITEA_URL`, sandbox/test instances): the canonical `recipe-upstream` script in
|
||||
`references/recipe-maintainer/.claude/commands/recipe-upstream.md` runs as written. Note its
|
||||
WORKSPACE probing expects `/workspace` or `~/Documents/recipe-maintainer`; on a bare host pass
|
||||
the submodule dir explicitly instead.
|
||||
- **Non-sandboxed** (no ARM env on the host — e.g. the orchestrator, where the recipe-maintainer
|
||||
checkout is only a pinned submodule): no sandbox/test instances are needed and NONE of the
|
||||
setup skills are. Recipe-maintainer mirrors on `git.autonomic.zone` are publicly readable, so:
|
||||
1. Check out the recipe if missing: `abra recipe fetch <recipe>` (lands in `~/.abra/recipes/<recipe>`)
|
||||
— or a plain anonymous `git clone https://git.autonomic.zone/recipe-maintainers/<recipe>.git`
|
||||
if abra is unavailable.
|
||||
2. Fetch the PR head branch from the mirror **anonymously** — no credentials in the remote URL:
|
||||
`git remote add gitea https://git.autonomic.zone/recipe-maintainers/<recipe>.git`
|
||||
(remote update rather than re-add if it exists), then
|
||||
`git fetch gitea +refs/pull/<N>/head:refs/heads/<head_ref>`.
|
||||
3. Fetch PR metadata (head/base refs, merged flag, release bump line) from
|
||||
`https://git.autonomic.zone/api/v1/repos/recipe-maintainers/<recipe>/pulls/<N>` —
|
||||
unauthenticated; use bot creds only if the repo turns out to be private (orchestrator hosts
|
||||
can read them from `/srv/cc-ci-orch/.testenv` — never written anywhere else).
|
||||
4. Everything else in the canonical script (origin/dev remote setup, release recommendation,
|
||||
emitted next-steps) is identical.
|
||||
|
||||
Every time the branch was prepared **here**, remember it exists only on this host — the
|
||||
operator's machine must fetch it first. Always emit this **step 0** before the push step
|
||||
(anonymous public fetch, no credentials needed):
|
||||
|
||||
```
|
||||
# 0. On a machine WITHOUT the branch pre-fetched, get it from the autonomic mirror
|
||||
# (assumes a local checkout of the recipe that already has the mirror remote;
|
||||
# its name here is `gitea`):
|
||||
cd <local checkout of the recipe>
|
||||
git fetch gitea +refs/pull/<PR_NUM>/head:refs/heads/<HEAD_REF>
|
||||
git checkout <HEAD_REF>
|
||||
```
|
||||
|
||||
If the operator's checkout does NOT yet have the mirror remote, emit once before the fetch:
|
||||
|
||||
```
|
||||
git remote add gitea https://git.autonomic.zone/recipe-maintainers/<recipe>.git
|
||||
```
|
||||
|
||||
In both modes the final output is a set of commands for the operator to run on a machine **with
|
||||
push access to `git.coopcloud.tech`** — always print them, even when everything local is
|
||||
already prepared.
|
||||
|
||||
@@ -43,7 +43,10 @@ On cc-ci, after `abra recipe fetch <recipe>`, read `~/.abra/recipes/<recipe>/`:
|
||||
### 2. Create + sync the mirror
|
||||
|
||||
Create `recipe-maintainers/<recipe>` (Gitea API: `POST /orgs/recipe-maintainers/repos`,
|
||||
`{"name":…,"private":true,"default_branch":"main","auto_init":false}`), then force-sync from
|
||||
`{"name":…,"private":false,"default_branch":"main","auto_init":false}`) — a recipe mirror MUST be
|
||||
created **public**: the cc-ci recipe mirrors are public (fleet invariant; the report's live PR-STATUS
|
||||
proxy `report./pr/<recipe>/<n>` is tokenless and only sees public repos — private-from-birth mirrors
|
||||
like gitea/wordpress left its status cells dark until flipped). Then force-sync from
|
||||
coopcloud with the existing helper **run on cc-ci with creds injected via stdin** (the host has
|
||||
no `.testenv`):
|
||||
```
|
||||
|
||||
+37
-1
@@ -31,8 +31,44 @@ handoff).
|
||||
|
||||
---
|
||||
|
||||
## Session 2026-05-31 ~18:30 UTC — Claude Sonnet 4.6
|
||||
## Session 2026-09-14 ~16:45 UTC — opencode glm-5.3-flash (orchestrator) — round 2: Anubis UA
|
||||
|
||||
**Left off:** The real root cause turned out to be TWO independent layers; the mirror-privacy
|
||||
fix (earlier session entry today) was necessary but not sufficient. Operator's browser console
|
||||
showed CORS failures redirecting to `anubis.swarm.autonomic.zone/.within.website/?redir=…`.
|
||||
Reproduced exactly: the `/pr/` proxy forwards the END browser's User-Agent to Gitea; Gitea sits
|
||||
behind **Anubis**, which 307-challenges browser-like UAs to `anubis.swarm.autonomic.zone`
|
||||
(no CORS headers) → every fetch throws in the browser → all cells "?" (curl passed clean, which
|
||||
is why server-side checks and my earlier headless test never saw it — intermittent/rate-dependent
|
||||
for my playwright run). Fix: cc-ci **PR #38** adds
|
||||
`proxy_set_header User-Agent "ccci-reports-proxy/1.0";` to the reports.nix `/pr/` location.
|
||||
Hot-verified on the host by mount-swapping a fixed conf into the running task (one mis-step:
|
||||
`--mount-rm`+`--mount-add` same-target order wiped the mount; re-added), scoped live, all 16
|
||||
cells rendering with a real Chromium. Merged PR #38, `nix flake update cc-ci`,
|
||||
`nixos-rebuild test` → healthy (reports 200, no failed units) → `switch` (flake.lock commit
|
||||
9e7770f). Final verify: browser-UA curl 200 both gitea/9 + full headless-Chromium sweep 16/16
|
||||
OPEN, zero non-200 /pr fetches. Also flipped memory: `memory/gitea-anubis-ua-challenge.md` +
|
||||
MEMORY.md index.
|
||||
**Open:** nothing blocking; next weekly /recipe-report and STATUS live-checks carry the fix.
|
||||
|
||||
## Session 2026-09-14 ~15:00 UTC — opencode glm-5.3-flash (orchestrator)
|
||||
|
||||
**Left off:** Report STATUS column fix. Operator reported the week-2026-09-11 report's live
|
||||
PR-STATUS column all "?" — root cause: the tokenless same-origin proxy
|
||||
`report./pr/<recipe>/<n>` (cc-ci `nix/modules/reports.nix`) 404s on **private** mirrors; two
|
||||
late-enrolled mirrors, `recipe-maintainers/gitea` (2026-06-11) and `wordpress` (2026-08-03),
|
||||
had been created `"private":true` from birth — by the stale instruction in
|
||||
`/recipe-enroll`'s mirror step (the other 21 mirrors were flipped public on 2026-06-09, and
|
||||
the old 'org is private' blocker is long resolved). Fixed: secret-scanned both repos, flipped
|
||||
`private=false` (PATCH with bot creds), patched `.opencode/skills/recipe-enroll/SKILL.md` to
|
||||
create mirrors `private:false`, updated memory/recipe-mirrors-public-org-blocker.md +
|
||||
MEMORY.md index. **Verified in a real headless Chromium (nixpkgs chromium + playwright)**:
|
||||
all 16 STATUS rows render `open`, every `/pr/` fetch 200 JSON. Commit 6e93922 pushed. The
|
||||
STATUS column refreshes live every 30s; cells go ✓ when a PR merges. No reports.nix change
|
||||
was needed (proxy itself was healthy).
|
||||
**Open:** nothing on this; the report index regenerates next weekly run.
|
||||
|
||||
## Session 2026-05-31 ~18:30 UTC — Claude Sonnet 4.6
|
||||
**Left off:** Got opencode/deepseek-v4-pro working as the loop backend. Both builder and
|
||||
adversary are actively running on `tinfoil/deepseek-v4-pro` (via `inference.tinfoil.sh`).
|
||||
Phase 5 [11/11] in progress. The operator is debugging the opencode web UI visibility and
|
||||
|
||||
Generated
+4
-4
@@ -10,11 +10,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1788820034,
|
||||
"narHash": "sha256-mkktTMeGKutfPp3pn3AhoPoGSCm4iRSnPvpmbEpMmwA=",
|
||||
"lastModified": 1789404337,
|
||||
"narHash": "sha256-FY5oOz/C6i6Ct6Qe6DqN6nOq+TXCyiZq5Am1LKLpDss=",
|
||||
"ref": "refs/heads/main",
|
||||
"rev": "b11cc0b7385aee9fb89bd9a7ed23896401d683e5",
|
||||
"revCount": 1543,
|
||||
"rev": "eb5fb826114f2b141dffb270fd36f7a22d1bf343",
|
||||
"revCount": 1550,
|
||||
"type": "git",
|
||||
"url": "https://git.autonomic.zone/recipe-maintainers/cc-ci.git"
|
||||
},
|
||||
|
||||
+2
-1
@@ -3,7 +3,8 @@
|
||||
- [Orchestrator host: Hetzner](orchestrator-host-hetzner.md) — runs on Hetzner cpx22; rebuild cmd, loops-service bounce, git-identity gotcha
|
||||
- [Push commits to remote](push-commits-to-remote.md) — push to git.autonomic.zone right after every commit in this repo
|
||||
- [Regression canary cadence](regression-canary-cadence.md) — server E2E canaries run on polish/review/release, not every commit
|
||||
- [Recipe-mirrors public / org blocker](recipe-mirrors-public-org-blocker.md) — mirrors public but recipe-maintainers ORG is private → live PR-STATUS column dark until operator flips org public
|
||||
- [Recipe-mirrors MUST be public](recipe-mirrors-public-org-blocker.md) — org public since ~2026-06, but /recipe-enroll created late mirrors (gitea, wordpress) private-from-birth, darkening report STATUS cells; flipped public + skill fixed 2026-09-14
|
||||
- [Gitea Anubis UA challenge](gitea-anubis-ua-challenge.md) — git.autonomic.zone 307-challenges browser UAs to an un-CORS-able origin; server-side proxies representing browser JS must pin a non-browser UA (cc-ci PR #38, 2026-09-14)
|
||||
- [abra chaos-deploy checkout gotcha](abra-chaos-deploy-checkout-gotcha.md) — `abra app new` moves recipe checkout to release tag; checkout PR branch after, or chaos deploys wrong tree
|
||||
- [Shared recipe-checkout race](shared-recipe-checkout-race.md) — never git-checkout ~/.abra/recipes/<recipe> on cc-ci while its CI build runs; harness deploys from that tree
|
||||
- [immich pgvecto.rs DROP DATABASE panic](immich-pgvectors-drop-database-panic.md) — DROP DATABASE crashes immich's postgres image; use pg_dump --clean --if-exists + search_path rewrite
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
name: gitea-anubis-ua-challenge
|
||||
description: "Gitea (git.autonomic.zone) sits behind Anubis, which 307-challenges browser-like User-Agents to an un-CORS-able counter-domain — any server-side proxy that forwards a browser UA on behalf of client JS breaks; pin a non-browser UA"
|
||||
metadata:
|
||||
node_type: memory
|
||||
type: project
|
||||
---
|
||||
|
||||
`git.autonomic.zone` sits behind **Anubis** (`anubis.swarm.autonomic.zone`). Anubis
|
||||
307-challenges requests whose User-Agent looks like a real browser to
|
||||
`/.within.website/?redir=…` — a JS proof-of-work challenge page on a *different* origin with
|
||||
**no CORS headers**.
|
||||
|
||||
Impact: any server-side proxy that serves client-side JS but forwards the *end browser's* UA
|
||||
hits this asymmetry — `curl` (non-browser UA) passes through cleanly, a real Firefox/Chrome UA
|
||||
gets the 307. Concretely: the Recipe Report's same-origin PR-STATUS proxy
|
||||
(`report./pr/<recipe>/<n>`, cc-ci `nix/modules/reports.nix`) forwarded the browser UA, so
|
||||
every live cell in the browser rendered `?` (week-2026-09-11, operator-CORS console report).
|
||||
|
||||
Fix (cc-ci PR #38, merged 2026-09-14): `proxy_set_header User-Agent "ccci-reports-proxy/1.0";`
|
||||
in the `/pr/` nginx location — a stable non-browser UA passes Anubis unmolested. Deployed via
|
||||
`nix flake update cc-ci` → `nixos-rebuild test` → health → `switch` (flake.lock commit).
|
||||
|
||||
Rule of thumb: server-side callers of the Gitea API (scripts, proxied fetches, harness code
|
||||
running in a browser's name) must pin a non-browser User-Agent or cookie-carry; browser-visit
|
||||
flows keep the JS challenge and that's fine. Related: [[recipe-mirrors-public-org-blocker]].
|
||||
@@ -1,29 +1,26 @@
|
||||
---
|
||||
name: recipe-mirrors-public-org-blocker
|
||||
description: "Recipe mirrors are public repos but the recipe-maintainers ORG is private-visibility, so anon reads 404; bot can't flip the org"
|
||||
description: "Recipe mirrors MUST be public (fleet invariant: the report's tokenless PR-STATUS proxy only sees public repos); org is public since ~2026-06, but late-enrolled mirrors (gitea, wordpress) were created private-from-birth by /recipe-enroll — fixed 2026-09-14"
|
||||
metadata:
|
||||
node_type: memory
|
||||
type: project
|
||||
originSessionId: f7960036-d990-4a21-a81e-f7c486d97fea
|
||||
---
|
||||
|
||||
As of 2026-06-09 all 21 recipe mirrors under `recipe-maintainers` were flipped `private=false`
|
||||
(secret-scanned first), to power the Recipe Report's live PR-STATUS column via the tokenless
|
||||
same-origin proxy `report.ci.commoninternet.net/pr/<recipe>/<n>` (shipped in cc-ci
|
||||
`nix/modules/reports.nix`). BUT the **org itself is `visibility: private`**, which makes Gitea 404
|
||||
all its repos for anonymous users — so the live STATUS column shows a muted "?" instead of open/✓.
|
||||
The Recipe Report's live PR-STATUS column (`report.ci.commoninternet.net/pr/<recipe>/<n>`,
|
||||
shipped in cc-ci `nix/modules/reports.nix`) is a tokenless same-origin nginx proxy to the Gitea
|
||||
API — it can ONLY see public repos. So **`recipe-maintainers/<recipe>` mirrors MUST be public**
|
||||
(fleet invariant; cc-ci/cc-ci-orchestrator/archived repos stay deliberately private).
|
||||
|
||||
**Blocker:** `autonomic-bot` cannot flip the org (PATCH `/orgs/recipe-maintainers` → 403 "Must be an
|
||||
organization owner"; `is_admin=false`; the basic-auth credential lacks `write:organization` scope,
|
||||
even though the bot is in the Owners team). Confirmed model: `autonomic-cooperative` is a public org
|
||||
and its repos ARE anonymously visible; `recipe-maintainers` is private and they are not.
|
||||
History: originally ALL mirrors were private; on 2026-06-09 the 21 then-existing mirrors were
|
||||
flipped `private=false` after a secret-scan, and the `recipe-maintainers` org was later flipped
|
||||
public by the operator (the old 'org is private' blocker is RESOLVED).
|
||||
|
||||
**Why:** the whole live-status feature is dark until this is resolved. Private repos stay hidden even
|
||||
in a public org, so flipping the org public does NOT expose the four locked-private repos (`cc-ci`,
|
||||
`cc-ci-secrets`, `cc-ci-orchestrator`, `archived-cc-ci-orchestrator`).
|
||||
|
||||
**How to apply:** operator (an org owner) must set `recipe-maintainers` org visibility to **public**
|
||||
in the Gitea UI (Settings → make org public), OR provision a token with `write:organization` scope.
|
||||
The instant that happens, the proxy returns 200 PR JSON and the column lights up — no redeploy needed.
|
||||
Verify: `curl https://report.ci.commoninternet.net/pr/cryptpad/5` should return PR JSON, not a 404.
|
||||
Recurrence (2026-09-14): /recipe-enroll still carried the old instruction to create mirrors with
|
||||
`"private":true` — so the later-enrolled mirrors `gitea` (2026-06-11) and `wordpress` (2026-08-03)
|
||||
were **private from birth**, leaving the report STATUS cells for those rows permanently "?"
|
||||
(gitea/wordpress rows in week-2026-09-11). Fixed: secret-scanned both, flipped `private=false`
|
||||
via PATCH `/repos/recipe-maintainers/<r>`, patched the skill to create `private:false`. The
|
||||
report now lights up for every row — verify any new PR row with
|
||||
`curl https://report.ci.commoninternet.net/pr/<recipe>/<n>` returning PR JSON, not 404.
|
||||
Related: [[push-commits-to-remote]].
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
---
|
||||
name: weekly-upgrader-opencode-go-glm
|
||||
description: weekly upgrade run now defaults to opencode-go/glm-5.3-flash (PR #20, 2026-09-14); zen tier dead on this host; how backend/model + monitoring are wired
|
||||
metadata:
|
||||
node_type: memory
|
||||
type: project
|
||||
originSessionId: 85355980-5e4f-4f90-b1ca-d0e4fe82f04b
|
||||
---
|
||||
|
||||
The weekly recipe-upgrade run (`cc-ci-plan/launch-upgrader.py`, fired by the
|
||||
`cc-ci-upgrade-all` systemd timer Thu 22:00 Boston) now **defaults to backend=opencode,
|
||||
model=opencode-go/glm-5.3-flash** when no env is set (operator choice 2026-09-08 via
|
||||
`/srv/cc-ci/upgrader.env`; PR #20 hardcoded the same defaults into the launchers 2026-09-14 —
|
||||
for all three launchers: upgrader, report, supervisor). Model default tracks the
|
||||
backend — an explicit `LOOP_BACKEND=claude` (no model) falls back to `sonnet`.
|
||||
|
||||
**2026-09-14 — ZEN IS DEAD ON THIS HOST.** The ZEN provider models (`opencode/glm-5.2`) are
|
||||
gone from the provider catalogue AND the host holds no ZEN key, so any launch that bypassed
|
||||
upgrader.env died instantly with `ProviderModelNotFoundError: opencode/glm-5.2` — this is what
|
||||
killed the 2026-09-11 report agent at launch and every manual retry (all valid ZEN credentials
|
||||
mean nothing without the ZEN models existing server-side). If a launcher log shows
|
||||
`ProviderModelNotFoundError`, the model id does not resolve on this host: run
|
||||
`opencode models | grep glm` for what does, then fix the tier/model default. See PR
|
||||
recipe-maintainers/cc-ci-orchestrator#20.
|
||||
|
||||
**Provider:** "OpenCode **Go**" — a flat $10/mo subscription, DISTINCT from "opencode Zen"
|
||||
(pay-as-you-go). Endpoint `https://opencode.ai/zen/go/v1/` (OpenAI-compatible), model id
|
||||
**`opencode-go/glm-5.2`** (Zen tops out at glm-5.1 — `opencode.ai/zen/v1` does NOT have 5.2;
|
||||
updating the opencode CLI does NOT change this, the catalog is server-side). API key lives in
|
||||
`~/.local/share/opencode/auth.json` (mode 600, entry `opencode-go` type api) — **never in the
|
||||
repo**. opencode CLI installed: 1.15.13.
|
||||
|
||||
**Override per run without a rebuild:** drop `/srv/cc-ci/upgrader.env` (read via
|
||||
`EnvironmentFile=-` on the unit) with e.g. `LOOP_BACKEND=claude` / `LOOP_MODEL=sonnet`. Absent
|
||||
file → the glm-5.2 default.
|
||||
|
||||
**Monitoring (opencode backend):** the launcher does `opencode run -m <model> --share --attach
|
||||
http://127.0.0.1:4096`. `opencode-web.service` (=`opencode serve` :4096, nginx →
|
||||
http://oc.commoninternet.net, tailnet-only) must be active. `OPENCODE_SHARE=1` (default) also
|
||||
mints a public `https://opncd.ai/share/<id>` link (printed in the TUI / readable from the server
|
||||
API `GET /session/<id>` → `.share.url`). Note: launch-upgrader.py's `session_busy()` regex is
|
||||
claude-tuned, so it misreports an active opencode run as "idle/finishing" — check the process
|
||||
(`opencode run`) or web UI instead.
|
||||
|
||||
Gotcha fixed: opencode `-m/--model`/`--attach`/`--share`/`--title` are flags on the `run`
|
||||
SUBCOMMAND — they must come AFTER `run` (a global `opencode --model X run` is silently ignored).
|
||||
|
||||
`launch-report.py` also defaults to opencode-go/glm-5.2 now (overridable via REPORT_BACKEND/REPORT_MODEL).
|
||||
In the weekly flow the upgrader writes the summary inline AND auto-launches the report (upgrade-all §6).
|
||||
|
||||
**Usage limit + auto-resume watchdog (added 2026-06-23):** opencode-go enforces a ROLLING usage window
|
||||
(~5h) capped by dollar value. A full glm-5.2 upgrade run is heavy — ~8 recipes (≈2h20m wall) exhausted
|
||||
it mid-run, returning HTTP 429 + `retry-after` (seconds) on the go endpoint. When limited, the
|
||||
`opencode run` agent loop ENDS and does NOT self-resume (process lingers but idle). Fix: `launch-upgrader.py`
|
||||
now has `resume` (continue the SAME session, context preserved, via `opencode run -s <id> --continue` —
|
||||
finds the session from the web server, kills the idle proc via /proc scan NOT `pkill -f` which self-matches)
|
||||
and `watchdog` (polls the session log; on >15min stall, waits out the 429 retry-after then auto-resumes).
|
||||
An opencode `start` auto-spawns the watchdog; logs to `cc-ci-upgrader-watchdog.log`. To resume by hand:
|
||||
`python3 /srv/cc-ci/cc-ci-plan/launch-upgrader.py resume`. (Subcommand `watchdog`, matching agents.py —
|
||||
renamed from `babysit` 2026-06-23.)
|
||||
|
||||
**Watchdog covers ALL parts of the weekly run (2026-06-23):** (1) it runs in a tmux session
|
||||
(`<session>-watchdog`) under the persistent tmux server, NOT a Popen child — so it survives the
|
||||
`cc-ci-upgrade-all` systemd Type=oneshot whose cgroup is reaped on exit (a Popen child would be killed).
|
||||
(2) The watchdog is generic (env: `UPGRADER_SESSION/_MODEL/_DONE_MARKER/_RESUME_FILE`), so `launch-report.py`
|
||||
reuses it for the REPORT job (`cc-ci-report` session, marker `RECIPE REPORT COMPLETE`) — the report runs on
|
||||
glm-5.2 sharing the same opencode-go budget the upgrade run drains, so it can 429-stall too. `_run_pids()`
|
||||
is scoped to the managed session (title or `-s <sid>`) so the two watchdogs never kill each other's process.
|
||||
Weekly timer: Thu 22:00 America/New_York; no `LOOP_BACKEND`/`upgrader.env` override ⇒ glm-5.2 defaults.
|
||||
See [[orchestrator-backend-switch-gotcha]].
|
||||
|
||||
**2026-07-04 hardening (after the 2026-07-03 run stalled on a HOST DISK-FULL and never resumed —
|
||||
the log-idle/429 watchdog does NOT cover an environmental wedge).** Three changes (commits 1bd156e,
|
||||
399e999), host rebuilt:
|
||||
- **Step-0 stale-image pre-reclaim** (`launch-upgrader.py` `prereclaim_cc_ci()`): before each weekly
|
||||
run, `ssh cc-ci docker image prune -af --filter until=168h` — only images unused AND >1 week old, so
|
||||
this week's likely-reused images stay. Best-effort; env `UPGRADER_PRERECLAIM*`. A full run's images
|
||||
pile up week-over-week and had run root to 100%. (The 2026-07-03 pile was 93 GB / 194 images; pruning
|
||||
took it 88%→17%.)
|
||||
- **watchdog proc-death detection**: an `opencode run` proc EXITS when the model ends its turn, so a
|
||||
long /upgrade-all dies+restarts many times before completing (and the log mtime freezes on death,
|
||||
making log-idle useless). `watchdog()` now resumes on "no live run proc for the session + not
|
||||
`_completed()`", not just log-idle. Capped at `UPGRADER_MAX_RESUMES` (20) → hands off if stuck.
|
||||
`resume()` now auto-spawns a watchdog if none is alive (skips when the watchdog itself called it).
|
||||
- **Hourly glm-5.2 SUPERVISOR** (`launch-supervisor.py`, systemd `cc-ci-upgrade-supervisor` .service +
|
||||
.timer, hourly at :07): cheap deterministic gate — no-ops (0 tokens) if the run is complete /
|
||||
progressing / a per-run watchdog is alive; only when a run stalled+died AND the watchdog gave up does
|
||||
it launch a short-lived glm-5.2 agent to diagnose (disk/wedge) + drive to completion. `force`/`status`/
|
||||
`stop` subcommands. Progress judged by `lu._run_pids(sid)` + log mtime (session_busy() is claude-tuned,
|
||||
misreads a headless opencode run as idle — do NOT use it for opencode). Window guard: won't
|
||||
auto-resurrect a session >96h old. Overrides via `/srv/cc-ci/upgrader.env` (e.g. `SUPERVISOR_MODEL=`).
|
||||
Ownership: **watchdog = prompt recovery (single writer while alive); supervisor = backstop when the
|
||||
watchdog is absent/gave up.** They're mutually exclusive (gate defers while watchdog tmux alive).
|
||||
|
||||
**2026-07-04 finish + more watchdog fixes (commits 69dd178, ddedea8, b6a95c4).** Finishing the stalled
|
||||
2026-07-03 run exposed that a glm-5.2 `opencode run` is FLAKY for long autonomous jobs — its turn ENDS
|
||||
(proc exits) repeatedly, `--continue` of a bloated session often doesn't advance, and it can wedge on a
|
||||
hung subagent. Bugs fixed in `launch-upgrader.py`: (1) `_session_idle_min()` now measures staleness
|
||||
across the whole session TREE (parent + descendant subagents) via the opencode server time.updated —
|
||||
NOT the tmux log mtime (freezes on `--continue`) and NOT the parent alone (a 20-40min recipe subagent
|
||||
made the parent look idle → false-resume killed the productive run). (2) watchdog loop is wrapped in
|
||||
try/except so a transient blip can't kill it. (3) **`_completed()` false-positive**: it matched
|
||||
DONE_MARKER inside TOOL parts (a subagent `task` prompt / bash echoing "print UPGRADE RUN COMPLETE") →
|
||||
watchdog exited early, run wedged. Now requires the marker in the LAST assistant TEXT (prose) message.
|
||||
(4) supervisor gate also treats the run as done once the weekly summary file exists (a run can finish
|
||||
work+summary+report yet never print a clean marker). **Operational fallback that WORKED:** when the
|
||||
report agent wedged twice, published the report by hand from the deterministic path —
|
||||
`recipe-report.py survey <date>` → build the spec JSON from survey.json → `render` → `publish`. This
|
||||
is reliable and bypasses the flaky agent entirely; keep it as the go-to when an agent-driven report
|
||||
stalls. The 2026-07-03 report is live at report.ci.commoninternet.net/week-2026-07-03.html (16 GREEN,
|
||||
plausible + lasuite-drive RED, discourse 5-PR reconcile).
|
||||
@@ -50,6 +50,59 @@ run `/cctest-intro` / `/cctest-setup-sandbox` first.
|
||||
a deliberate submodule bump + `scripts/gen-cctest-skills.py` regeneration.
|
||||
"""
|
||||
|
||||
# Per-skill extra body sections appended after BODY_TEMPLATE (survive regeneration).
|
||||
PER_SKILL_NOTES: dict[str, str] = {
|
||||
"recipe-upstream": """
|
||||
**Sandboxed vs non-sandboxed mode.** This skill needs only git + (optionally) the Gitea API —
|
||||
not the test server — so it can run either way. Probe, then follow that branch:
|
||||
|
||||
- **Sandboxed** (ARM env configured: `test-ssh/.testenv` with `GITEA_USERNAME`/`GITEA_PASSWORD`/
|
||||
`GITEA_URL`, sandbox/test instances): the canonical `recipe-upstream` script in
|
||||
`references/recipe-maintainer/.claude/commands/recipe-upstream.md` runs as written. Note its
|
||||
WORKSPACE probing expects `/workspace` or `~/Documents/recipe-maintainer`; on a bare host pass
|
||||
the submodule dir explicitly instead.
|
||||
- **Non-sandboxed** (no ARM env on the host — e.g. the orchestrator, where the recipe-maintainer
|
||||
checkout is only a pinned submodule): no sandbox/test instances are needed and NONE of the
|
||||
setup skills are. Recipe-maintainer mirrors on `git.autonomic.zone` are publicly readable, so:
|
||||
1. Check out the recipe if missing: `abra recipe fetch <recipe>` (lands in `~/.abra/recipes/<recipe>`)
|
||||
— or a plain anonymous `git clone https://git.autonomic.zone/recipe-maintainers/<recipe>.git`
|
||||
if abra is unavailable.
|
||||
2. Fetch the PR head branch from the mirror **anonymously** — no credentials in the remote URL:
|
||||
`git remote add gitea https://git.autonomic.zone/recipe-maintainers/<recipe>.git`
|
||||
(remote update rather than re-add if it exists), then
|
||||
`git fetch gitea +refs/pull/<N>/head:refs/heads/<head_ref>`.
|
||||
3. Fetch PR metadata (head/base refs, merged flag, release bump line) from
|
||||
`https://git.autonomic.zone/api/v1/repos/recipe-maintainers/<recipe>/pulls/<N>` —
|
||||
unauthenticated; use bot creds only if the repo turns out to be private (orchestrator hosts
|
||||
can read them from `/srv/cc-ci-orch/.testenv` — never written anywhere else).
|
||||
4. Everything else in the canonical script (origin/dev remote setup, release recommendation,
|
||||
emitted next-steps) is identical.
|
||||
|
||||
Every time the branch was prepared **here**, remember it exists only on this host — the
|
||||
operator's machine must fetch it first. Always emit this **step 0** before the push step
|
||||
(anonymous public fetch, no credentials needed):
|
||||
|
||||
```
|
||||
# 0. On a machine WITHOUT the branch pre-fetched, get it from the autonomic mirror
|
||||
# (assumes a local checkout of the recipe that already has the mirror remote;
|
||||
# its name here is `gitea`):
|
||||
cd <local checkout of the recipe>
|
||||
git fetch gitea +refs/pull/<PR_NUM>/head:refs/heads/<HEAD_REF>
|
||||
git checkout <HEAD_REF>
|
||||
```
|
||||
|
||||
If the operator's checkout does NOT yet have the mirror remote, emit once before the fetch:
|
||||
|
||||
```
|
||||
git remote add gitea https://git.autonomic.zone/recipe-maintainers/<recipe>.git
|
||||
```
|
||||
|
||||
In both modes the final output is a set of commands for the operator to run on a machine **with
|
||||
push access to `git.coopcloud.tech`** — always print them, even when everything local is
|
||||
already prepared.
|
||||
""",
|
||||
}
|
||||
|
||||
WRAPPER_TEMPLATE = """# {wrapped} (thin wrapper)
|
||||
|
||||
The canonical definition of this skill lives in the **opencode** position:
|
||||
@@ -99,7 +152,7 @@ def main() -> int:
|
||||
)
|
||||
canon = ROOT / ".opencode/skills" / wrapped / "SKILL.md"
|
||||
canon.parent.mkdir(parents=True, exist_ok=True)
|
||||
canon.write_text(frontmatter + BODY_TEMPLATE.format(name=name, wrapped=wrapped))
|
||||
canon.write_text(frontmatter + BODY_TEMPLATE.format(name=name, wrapped=wrapped) + PER_SKILL_NOTES.get(name, ""))
|
||||
thin = ROOT / ".claude/skills" / wrapped / "SKILL.md"
|
||||
thin.parent.mkdir(parents=True, exist_ok=True)
|
||||
thin.write_text(frontmatter + WRAPPER_TEMPLATE.format(name=name, wrapped=wrapped))
|
||||
|
||||
Reference in New Issue
Block a user