agentskills.codes

Install

mkdir -p .claude/skills/ci-zbynekdrlik && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/16692" && unzip -o skill.zip -d .claude/skills/ci-zbynekdrlik && rm skill.zip

Installs to .claude/skills/ci-zbynekdrlik

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

CI pipeline for camera-box — artifact download, Discord CI notifications, and probe binary flow. Load when working on CI (#25 notify-on-red, CI jobs, artifact download, or Discord notification delivery).
203 chars✓ has a “when” trigger

About this skill

CI

Artifact Download

# Main camera-box binary (Linux amd64)
gh run download --repo zbynekdrlik/camera-box -n camera-box-linux-amd64 --dir ./dist

# Probe tools — Linux (recording-verdict, frame-probe, camera-box-probe)
gh run download --repo zbynekdrlik/camera-box -n probe-tools-linux-amd64 --dir ./probe-bins

# Probe tools — Windows (recording-verdict.exe, obs-watchdog-gate.exe, obs-self-heal-gate.exe —
# the latter two are the #391/#411 OBS liveness-watchdog + self-heal gate binaries, needed on-box
# by scripts/obs-self-heal-install.sh's Task Scheduler recovery script)
gh run download --repo zbynekdrlik/camera-box -n probe-tools-windows-amd64 --dir ./probe-win

gh run download strips the +x bit — always re-add:

chmod +x ./probe-bins/*   # or ./dist/camera-box

Python harness tests (tests/python/)

CI job python-tests runs python -m pytest tests/python -v after pip install pytest websocket-client matplotlib (clean, minimal). These pin the obs_phase2.py pure helpers + arg parsing (no live OBS) — e.g. the _blackcheck_verdict, _diverging_locked_keys, and #328 _rpc_timed_out deadline helper.

Local gotcha: dev1's global pytest has a broken pytest-html plugin (missing jinja2) that aborts collection with ModuleNotFoundError: No module named 'jinja2'. CI is unaffected (its venv has only the three deps above). Locally, disable the plugin:

python3 -m pytest tests/python -q -p no:html -p no:cacheprovider

Discord CI Notifications (#25)

camera-box CI posts failures to Discord via the NewLevelMedia Discord bot REST API, channel #notifications🔔 (ID 1257652233714270219).

Mechanism: POST https://discord.com/api/v10/channels/<CHANNEL_ID>/messages with Authorization: Bot <token> and {content: "..."}. Discord returns HTTP 200 + the created message object — the id field is real delivery proof (unlike a relay "accepted" ack).

GitHub secrets: DISCORD_BOT_TOKEN (sourced from ~/.claude/channels/discord/.env)

  • DISCORD_CHANNEL_ID = 1257652233714270219. The notify-on-failure job (needs all gates, if: failure()) skips gracefully (exit 0) if either secret is unset.

DEAD PATH — do NOT reuse: CLAUDE_DISCORD_WEBHOOK_URL (the n8n relay at ~/.claude/env) ACCEPTS POSTs (HTTP 200 Workflow was started) but no longer DELIVERS to Discord (retired). HTTP 200 from it is a liveness ack, NOT delivery.

DM vs notifications channel:

  • 1491798759103795301 (DISCORD_NOTIFICATION_CHANNEL_ID in the channels .env) = bot↔user DM channel used by notify-discord.sh idle pings — this is a DM, NOT the shared #notifications channel. Cannot hold webhooks (404 Unknown Channel on the webhooks endpoint).

Lesson: a relay/webhook returning 2xx only means "accepted", never "delivered". Confirm via the created-message id in the response body.

The Discord bot can't fetch_messages on these channels from an in-session Discord plugin (not allowlisted) — verify delivery by the bot REST POST returning a message id.

Probe Binary Flow — Run on stream.lan, NOT dev1

OBS records the 0.7–6 GB program file on stream box (10.77.9.204 — strong CPU, fast disks). Decode WHERE THE VIDEO IS — run recording-verdict on stream against the LOCAL recording; bring back ONLY the small verdict JSON (+ pixel-proof PNGs). dev1 holds NOTHING big.

# 1. Download Windows verdict for the commit under test
gh run download --repo zbynekdrlik/camera-box -n probe-tools-windows-amd64 --dir ./probe-win

# 2. Upload to stream box via win-stream-snv MCP FileUpload:
#    path='C:\camera-box\recording-verdict.exe'

# 3. Run on stream box via win-stream-snv Shell (see scripts/recording-verdict-on-stream.sh)
scripts/recording-verdict-on-stream.sh \
  --verdict-exe 'C:\camera-box\recording-verdict.exe' \
  --out-dir 'C:\camera-box\verdict-out' \
  --stream-rec 'C:\path\on\box\stream-REC.mp4' \
  -- --strih 'C:\path\on\box\strih-REC.mkv' \
     --stream 'C:\path\on\box\stream-REC.mp4' \
     --min-secs 300 --json 'C:\camera-box\verdict-out\verdict.json' \
     --out-dir 'C:\camera-box\verdict-out\pixel-proof'

# 4. Pull back ONLY verdict JSON + pixel-proof PNGs via win-stream-snv FileDownload.

scripts/recording-e2e.sh defaults to this on-stream flow (VERDICT_ON_STREAM=1). VERDICT_ON_STREAM=0 selects the legacy decode-on-dev1 path (kept for boxes without uploaded verdict.exe).

scp/ssh to Windows (stream.lan) is DENIED on this rig — use the win-stream-snv MCP. ffmpeg/ffprobe are already installed on stream.lan (winget; ffmpeg 8.0.1 on PATH).

Probe is CI-only locally — never compile --features probe on dev1 (#185)

The shared dev1 target/ has no GC (rust-lang/cargo#5026). Compiling the heavy probe feature locally (qrcode/rqrr/image/drm/lz4_flex + 5 required-features=["probe"] bins) across the day's workers ballooned it to 18GB and filled the disk. The fix: local Tier-0 cheap-checks run DEFAULT features onlycargo clippy --all-targets -- -D warnings (NO --all-features), cargo test --no-run. Probe is compile-checked + built ON CI (ci.yml runs --all-features; #101 C++ gate + #192 probe-tools artifact). See CLAUDE.md "Local Build Policy".

Convention when adding an integration test that imports camera_box::probe::…: gate the whole file with #![cfg(feature = "probe")] (after the //! doc block, before the first use) — exactly like tests/recording_decode.rs. An UNGATED probe test forces the local default-feature clippy/test --no-run to compile the probe feature and re-balloons target/. CI (--all-features) still runs gated tests. tests/local_build_policy_bounds_target.rs enforces both invariants (it FAILS if any probe-using test is ungated or if the CLAUDE.md local gate command block uses --all-features/--features probe).

Gotcha — a clippy lint inside a probe-gated file (src/probe/*) is INVISIBLE to local Tier-0 clippy and only FAILS on CI's Lint job (which runs cargo clippy --all-targets --all-features). Default-feature local clippy never compiles src/probe/*, so a lint there passes locally and red-X's CI — a wasted CI cycle (#312/#324: a clippy::doc_lazy_continuation in recording_segments.rs module-doc passed Tier-0 but failed CI). When you edit ANY src/probe/* file — especially doc comments — eyeball it for clippy patterns before pushing; you cannot lint-verify it locally (running --all-features to check is banned — it re-balloons target/). Recurring doc trap: a [label]: token mid-paragraph (e.g. [`crate::x`]: text) makes pulldown-cmark parse a link-reference definition → the next lines become a doc_lazy_continuation lint; reword to drop the ]: (e.g. (in [`crate::x`] — text) or add a blank line.

Same class, a COMPILE error not a lint — a serde::Serialize (or any trait-bound) that only propagates under --features probe. bin/recording-verdict.rs's NodeVerdict is #[derive(serde::Serialize)], so EVERY field type must be Serialize. When you change a field to hold a lib-crate struct (e.g. #580 changed imag_optical_beat from Option<bool> to Option<OpticalBeatVerdict> so the printers/JSON could report the beat detail), that struct MUST ALSO derive Serialize — else the whole --features probe build breaks with E0277: the trait bound X: serde::Serialize is not satisfied, red-X'ing Lint + Test + Build + Coverage + Windows probe build at once. INVISIBLE to Tier-0: cargo check/clippy on default features never compiles bin/recording-verdict.rs (required-features = ["probe"]), so it passes locally and only fails on CI (one wasted cycle, #580). Before pushing a NodeVerdict (or any #[derive(Serialize)] probe-struct) field-TYPE change, add the matching derive to the new field's type in lib (serde is already a default, non-optional dep, so #[derive(..., serde::Serialize)] compiles on default features too) — you cannot catch this locally.

Backstop: scripts/install-git-hooks.sh installs a non-blocking pre-push hook running scripts/purge-target.sh (cargo clean when target/ > ${THRESHOLD_MB:-4096}; SKIPS while a live E2E has probe binaries running — matched by truncated /proc comm, e.g. recording-verdi, camera-box-prob, NOT a pgrep -f cmdline substring which false-positives on the script's own args).

airuleset pre-push-test-check — two recurring traps on git push origin dev

The global PreToolUse hook scans the WHOLE origin/main..HEAD delta on every push (not just your commits), so two things bite here repeatedly:

  1. Inherited bug-fix commit re-flagged. A fix(...)-prefixed commit already on dev but not yet on main (e.g. a prior [no-test:]-bypassed scripts/ fix from another cycle) is the FIRST commit in the delta → Gate-2 ("bug-fix commit before any test commit") re-fires on YOUR PR even though your own work is correctly RED→GREEN ordered. Resolve with a one-line [no-test: <reason>] on your LATEST commit (a docs/log commit is the natural carrier), naming the inherited commit as the only test-less one in range.
  2. [no-test: <reason>] must be on ONE LINE. The hook greps \[no-test:\s*[^]]+\] per-line, so a reason that wraps across newlines never matches and the push stays blocked (looks like the bypass is ignored). Keep the [no-test: …] opening [ and closing ] on the SAME commit-message line. Bare [no-test] (no reason) is rejected outright. Every bypass is logged to ~/devel/airuleset/audits/no-test-skips.log.

dev→main PRs can get auto-opened + auto-merged by something OTHER than you (#423 observation)

While working #423, PRs #424 and #425 (dev→main, "Closes #N", auto-merged within seconds of the triggering dev CI run going green) appeared WITHOUT any gh pr create/gh pr merge call from that work session. No .github/workflows/* file creates or merges cross-branch PRs (checked — release.yml only triggers on


Content truncated.

Search skills

Search the agent skills registry