cgagentharness-gotchas
Session-tested traps for working on CG-agent-harness. Load before install/verify, desktop packaging, Chrome acceptance CI, clippy/toolchain fights, write-gate debugging, or when something hangs or loo
SKILL.md.claude/skills/cgagentharness-gotchas/SKILL.md
GitHubCG-agent-harness gotchas
Session-process traps for this repo. Canonical contracts and the standing traps live in AGENTS.md /
INVARIANTS.md; this file holds only what they do not: the "looks green for the wrong reason" list.
Paths are relative to the repo root. Skills are repository guidance,
not runtime /api/skills plugins.
1. Clippy is the wrong binary on some Macs
- Symptom:
scripts/verify-local.shfails clippy with toolchain/proxy skew; rustup'scargo-clippyolder than Homebrew's. - Wrong fix:
#allowthe warnings or drop-D warnings. - Right fix:
CLIPPY="/opt/homebrew/bin/cargo-clippy clippy" scripts/verify-local.sh; barecargo-clippyskips--all-targets. - Evidence:
scripts/verify-local.sh.
2. Chrome chat-browser acceptance flakes
- Symptom: CI red on chat-browser acceptance; intermittent "Chrome didn't start" / session attach failures.
- Wrong fix: Delete the acceptance job or weaken assertions until green.
- Right fix: Treat as known flake class (issue #43 — Start Chrome reliably). Retry/quarantine with tracking; fix the starter, don't hollow the test.
- Also seen: a non-startup flake in the
chat-browser-acceptance.mjsassertion that thesaved-DEEPAGENT_API_KEYinput is empty after saving: it still held the typed value after a refresh. Observed once in eleven local runs; the same commit then passed ten straight, so do not attribute it to your diff on n=1. Mechanism unconfirmed. The one candidate worth checking first: the script launches Chrome without--password-store=basicor any autofill-disabling flag, and the field istype="password". Confirm before "fixing" it. - Evidence: CI gotcha / #43.
3. Desktop packaging: lipo / sign / embed-SHA256 order
- Symptom: Checksums never match after local resign; Gatekeeper weirdness; sidecar digest verification fails.
- Wrong fix: Re-sign the sidecar after digest embed; mix ad-hoc and Developer ID steps without documenting which artifact is which.
- Right fix: Order is lipo → sign → embed-SHA256. Never re-sign a sidecar
after digest embed. Ad-hoc (
--sign -) ≠ Developer ID / notarized. WKWebView shell ≠ HTTP console semantics — don't assume fetch/CSRF behavior is identical inside the native wrapper. - Evidence:
scripts/package-release.sh(ad-hoc note);docs/RELEASING.md.
4. Seatbelt permission errors ≠ app regression
- Symptom: Sandbox verify fails with permission denied; patch softens the Seatbelt profile or skips sandbox tests.
- Wrong fix: Weaken assertions or broaden the profile to silence noise.
- Right fix: Reproduce with owned temp
CGAGENTHARNESS_HOME+ unique port. Distinguish environment/Seatbelt noise from product bugs. No backend ⇒ exit 3 remains correct fail-closed behavior. - Evidence:
INVARIANTS.mdjudged-before-land;agentic_foundationssandbox_* tests.
5. Shared listener ≠ this bundle
- Symptom: Smoke "passes" against a leftover
serveon:8790from another checkout or build. - Wrong fix: Hit the default port and assume it's your binary.
- Right fix: Owned temp
CGAGENTHARNESS_HOME, unique port, kill/own the process you started (scripts/smoke-ollama.shpattern). - Evidence:
scripts/smoke-ollama.sh.
6. github.paginate result consumed as {data}
- Symptom: A workflow step dies with
TypeError: Cannot read properties of undefined (reading 'find'), and the check fails on every PR rather than intermittently. - Wrong fix: Wrap the access in
?.and move on, or revert to the unpaginated call that only ever saw page 1 of 30. - Right fix:
github.rest.issues.listComments(...)resolves to{data: [...]};github.paginate(github.rest.issues.listComments, ...)resolves to the array itself. When switching topaginate, change the consumer too —comments.find(...), notcomments.data.find(...). Audit everyawait github.paginatecall site in.github/workflows/at once; there are four, and they must all read the array directly. - How to check before pushing: the
github-scriptblock can be extracted from the YAML and run under stubs (github,context,coreare just arguments), withlistCommentshonouring the 30-per-page default and a marker planted on page 2. Cheaper than a CI round trip, and it reproduces both the original duplicate-comment bug and theTypeError. - Evidence:
.github/workflows/pr-template-check.yml— its two jobs disagreed on this for a while; thebase-branchjob was always correct.
7. Ephemeral port handed to a child, and serve exits 0 when it is taken
- Symptom: Intermittent red in
scripts/test-desktop-backend.pywithAssertionError: 0 is not None : headless startup exited, orAssertionError: 0 == 0— neither of which mentions ports. - Wrong fix: Widen a timeout, retry the whole job, or call it a runner problem.
- Right fix: Binding
:0, reading the port back and closing the socket is inherently advisory — the reservation must be released before the child can bind, so anything on the runner can take it in between. Retry on a fresh port. Two traps make this worse than it looks:serveexits 0 on port-in-use, printing "CGagentHarness may already be running on 127.0.0.1:PORT" to stdout (stderr empty), becauseport_in_useis checked before.envis read. So a test asserting a non-zero refusal fails, and the path it meant to exercise never runs.- Do not truncate captured output between attempts if a later assertion
reads that file. The credential-disclosure
assertNotInchecks in that suite read the child's captured stdout/stderr; truncating means a leak on a failed attempt is erased and the check passes on a clean retry.
- Related discipline: when you fix one instance of a race, re-audit every site you refactored as well as the one that was failing. Converting a second site to the shared helper without its retry leaves the bug live at a site your own commit touched.
- Evidence:
scripts/test-desktop-backend.py(free_port,await_headless,serve_expecting_refusal);ModelFixtureandtest_unrelated_listener_is_never_adoptedshow the keep-the-listener-open variant where that is possible.
8. A push does not re-trigger the Codex review
- Symptom: You address review findings, push, see the PR go green, and merge — with the fixes themselves never reviewed.
- Wrong fix: Assume the green check covers the new commits, or read a stale "Completed" summary row as a verdict on the current head.
- Right fix: Codex reviews trigger on open for review, mark draft ready,
and an explicit
@codex reviewcomment, not on a push. After pushing a fix, tell the operator the fixes are unreviewed and let them decide: never comment@codex reviewwithout their approval. If they approve, check that the Reviewed commit SHA matches your head. A superseded run can be reported ascancelledbycancel-in-progressconcurrency; that is not a failure, but it is also not a verdict. - Why it matters here: fixes to review findings are exactly the diffs most worth a second pass — they are written fast, under the assumption the problem is already understood.
- Evidence: the Codex "About Codex in GitHub" block on any PR in this repo.
9. The docs audit agent can cite lines that do not exist
- Symptom: A drift report says "CONSOLE.md line 15 says sessions are
written to
sessions/*.json"; the file has no such line. Applying the edit blindly fails, or worse, inserts prose at a guessed location. - Wrong fix: Trust a subagent's line numbers or quoted text as evidence.
- Right fix: Every audited claim is a hypothesis. Before editing,
grepthe quoted anchor in the named file; skip the finding if it does not match. Quote code values (config defaults, ranges, alias spellings) fromrg, never from the report. Thedoc-syncskill's "grep it" rule exists for this. - Evidence: 2026-09-16 docs refresh (PR #131): 1 of ~20 findings was fabricated; the other 19 were verbatim-verifiable.
10. Memory gate slash aliases are not the config key names
- Symptom: Docs (or a doc fix) write
/memory auto-retrieval onor/memory auto-consolidation on; the console rejects them. - Wrong fix: Derive the alias from the
structured_memory.*key. - Right fix: The console alias map is
gateMapinassets/static/harness.htmland the help line insrc/server/views.rs:capture|recall|retrieval|auto-retrieve|consolidation|auto-consolidate| auto-suggest-chat|auto-suggest-coding. The HTTP body forPOST /api/structured-memory/gatestakes the config name (episode_capture,explicit_recall,auto_retrieval, ...) instead. Two spellings, two surfaces; grep the one you are documenting.
11. Do not cut a release the maintainer already cut
- Symptom: Asked to "cut a release after the PR", you dispatch
release.ymlwithpublish=true; it fails with "Candidate tag already exists" or "Release already exists (including drafts)". - Wrong fix: Retry, delete the draft, or push a tag by hand.
- Right fix: Before any dispatch, list
release.ymlruns and releases. A tag push (pushevent,head_branch: vX.Y.Z) or a pendingworkflow_dispatchon the same SHA means the release is already in flight; watch that run instead. The planner (scripts/release-plan.py) fails closed on collisions by design. Also note: a greenBundlerun on the exact main SHA is a hard precondition forschedule/workflow_dispatchreleases. - Evidence: 2026-09-16,
v0.1.14tag push started run 26 while the docs PR was being drafted; dispatch run 27 queued behind it.
Related skills
Load cgagentharness-project-guidance for read order; cgagentharness-config-guard
before merging config diffs; cgagentharness-write-policy-redteam for write/git/argv/jail
loops; verification-specialist to break a supplied patch; fable-protocol for
evidence-first discipline. Before merging core-path diffs, ask the operator to run
/cgagentharness-invariant-guard (it ships disable-model-invocation: true).