Skip to content

fix(check): give the generated build steps a build-sized idle window - #124

Merged
github-actions[bot] merged 3 commits into
mainfrom
feat/image-build-bound
Oct 20, 2026
Merged

github-actions[bot] merged 3 commits into
mainfrom
feat/image-build-bound

Conversation

@aitrawog

@aitrawog aitrawog commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Fixes plugin-check#123: the generated root image-build step now sizes the CHILD's plugin-invoke idle window to a build, instead of leaving it at the project's readiness no_progress probe default (90s), where an IO-bound build phase is indistinguishable from a wedged call.

The mechanism (RCA). The bed runner runs the build step as charly box build through the cli seam. The seam deliberately sets no deadline on that child (hostBuildCli: "the context is NOT used as a deadline") and heartbeats the CALLER's activity clock while it runs. But the CHILD is itself a charly process whose own plugin dispatch (plugin-build) carries the plugin-invoke idle guard, and that guard's window is the project's readiness no_progress — 90s by default. A build's IO-bound phases (the OCI bridge's podman load, a cold layer export) make no host-visible progress for minutes, so the guard cancels the child and SIGKILLs the build:

charly: error: command "build": building arch.arch-builder: bridging arch.arch-builder into the podman store: buildkitdrive: load /home/atrawog/.cache/charly/buildkitd/export/ghcr.io_opencharly_arch-builder_check-crabbox-pod-2026.293.1030.oci.tar: podman load -i /home/atrawog/.cache/charly/buildkitd/export/ghcr.io_opencharly_arch-builder_check-crabbox-pod-2026.293.1030.oci.tar: signal: killed: Getting image source signatures
charly check run check-crabbox-pod: [image-build] FAIL after 2m2.279s

The fix. The root image-build step passes CHARLY_READINESS_NO_PROGRESS=30m to the child through the seam's per-call env — spec.CliRequest.Env, which the host cli handler merges over the inherited environment (the seam's placement-invariant carrier). 30m stays within the default absolute_cap (30m), so the child's readiness ordering validates without a second override. Non-build steps keep the project's windows.

The seam. The diff deletes the old bedCliCombined wrapper (its only caller was the step runner) and makes bedCliCombinedEnv the sole combined-capture seam, carrying both the combined capture and the per-call env. Round 2 correctly flagged that this rename was not accounted for in the body; it is now.

Scope — deliberately narrow. Only the ROOT image-build call site changed: it is the one the cited bed exercises, and the one whose death was measured. The member image-build-<member> and vm-build sites were rewired in round 1 and reverted here — neither has a run that would fail without the change. They are filed as plugin-check#125 with the measurement plan; the VM path is plausibly already safe because plugin-vm is out-of-process (its guard counts peer-tree CPU via the #699 reader), but that needs measuring, not assuming.

How tested

Head 4f130bab37786944dbf9d385d68b524f7d467946, tree de92d4917f42c7c9723855e2fa06078f4e374ddc. Gate: the repo's own (go build / go vet / go test in candy/plugin-check/, charly box validate at the root) plus the assembled R10 bed the fix unblocks (distro-cachyos check-crabbox-pod), run live on THIS tree's plugin compiled into a charly binary.

The final-tree gate

$ charly -C . box validate
charly box validate: OK — checked 1 candy, 4 deploys, 7 distros, 7 builders; 0 warnings, 0 errors
$ gofmt -l .
$ go build ./...
build rc=0
$ go vet ./...
vet rc=0
$ go test ./...
ok  	github.com/opencharly/plugin-check/candy/plugin-check	(cached)
?   	github.com/opencharly/plugin-check/candy/plugin-check/cmd/serve	[no test files]
test rc=0

TestBuildStepEnvSizesTheChildGuardWindow pins the env name/value and asserts the child's resolver ACCEPTS it (a rejected override silently falls back to the 90s default — the defect itself); it fails to compile without the change.

The live proof — the override reaches the child (final tree)

The plugin from THIS head was compiled into a worktree charly via a local replace (reverted after the run; the binary identifies as 2026.293.1057), and the bed was run with the PARENT's own window deliberately lowered to 20s — a window that would kill the same 44s build if the override did not reach the child:

$ CHARLY_PLUGIN_ONLY=1 CHARLY_BUILD_REGISTRY_MIRROR=127.0.0.1:5000 CHARLY_READINESS_NO_PROGRESS=20s /home/atrawog/Sync/Atrapub/lo/dsh/opencharly/.worktrees/crabbox-wave/charly/bin/charly check run check-crabbox-pod
charly check run check-crabbox-pod: [image-build] START (charly box build; log: .check/check-crabbox-pod/2026.293.1213/image-build.log)
charly check run check-crabbox-pod: [image-build] PASS after 44.614s
charly check run check-crabbox-pod: PASS (steps=14)
rc=0

The bed's own assertions also pass in-venue: crabbox --version reports 0.79.0 (cbx-verb-version) and the verb dispatches with 0.79.0.

The mechanism, bisected on the live host

  • Raised window (30m), full bed: 14/14 PASS (runs 2026.293.1139, 2026.293.1145).
  • Default window, unfixed plugin: killed at the bridge (2026.293.1030, 2m2s).
  • Only NO_PROGRESS/ABSOLUTE_CAP raised, probe bounds at defaults: 14/14 PASS — the guard window, not the probe bound, is the mechanism.

Rulebook compliance

  • R0 — /charly-check:check, /charly-internals:plugin, /charly-internals:git-workflow and this repo's AGENTS.md were loaded before the change.
  • R1 — the kill was root-caused (the guard; the daemon log's Solve … context canceled; the raised-window bisect). check run SIGKILLs the generated image-build step at the 120s never-hang bound, so a cold distro-base build can never pass #123 is fixed here; the member/vm sibling gap is filed as The member image-build and vm-build steps still run under the 90s probe window (unmeasured; #123 scoped to the root image-build) #125, separately measurable and unproven either way. Zero warnings in the gate.
  • R2 — one seam: one combined-capture body (bedCliCombinedEnv), one build-window definition (buildStepNoProgress/buildStepEnv); the dead wrapper is deleted rather than kept as a second path.
  • R3 — the value uses the mechanism's own documented knob (CHARLY_READINESS_NO_PROGRESS, resolved by the child's own ResolveReadiness); no sleep, retry, suppression, or global readiness widening.
  • R4 — the product is fixed: the harness stops killing a legitimate build.
  • R5 — the seam rename is complete: bedCliCombined no longer exists in the tree (the round-2 finding; the diff accounts for it).
  • R6 — fresh branch off origin/main; tree clean before and after; the proof build's local replace reverted; no force-push, no bypass.
  • R7 — the changed site fails without the change: the 20s-parent run above would kill the build without the override; the default-window cold build did (the 2m2s kill). The changed path ran live on the final tree.
  • R8 — the real bed ran against real builds and a real BuildKit daemon; nothing mocked.
  • R9 — N/A for this repo (no charly source change); the proof binary was a worktree build, never the shared install.
  • R10 — change class: command:check bed-runner behavior. The cited assembled bed ran on the final tree: 14/14, zero warnings; the one changed call site is exactly the one the bed exercises.

Change classification

Plugin command/bed-runner behavior + one unit test. Attribution tier: fully tested and validated — the repo gate passed on the final tree and the changed path ran live in the assembled bed.

Filed issues

  • Closes plugin-check#123 — the 90s probe-window kill on the generated root image-build step.
  • plugin-check#125 — the member image-build-<member> and vm-build siblings still run under the probe window; filed with the measurement plan (the VM path is plausibly safe via peer-tree CPU).

Agent: dsh-crabbox-079
Assisted-by: DeepSeek Harness deepseek-v4.1-flash (fully tested and validated)

The bed runner's image-build/vm-build steps run `charly box build`/`vm build`
through the cli seam. The CHILD's own plugin dispatch carries the plugin-invoke
idle guard, whose window is the project's readiness no_progress (default 90s); a
build's IO-bound phases — the OCI bridge's podman load, a cold layer export —
make no host-visible progress for minutes, so the guard read them as a wedged
call and SIGKILLed the build (plugin-check#123, measured: "bridging ... into the
podman store: podman load ...: signal: killed" at 2m2s).

The build steps now carry CHARLY_READINESS_NO_PROGRESS=30m to the child through
the seam's per-call env (spec.CliRequest.Env, merged over the inherited env by
the host cli handler). 30m stays within the default absolute_cap (30m), so the
child's readiness ordering validates without a second override; non-build steps
keep the project's windows.

Proven live: the distro-cachyos check-crabbox-pod bed, run with the fixed plugin
compiled into a worktree charly, PASSED 14/14 (image-build 44s) with the parent's
own window at 20s — a window that would have killed that same build without the
override. Test: TestBuildStepEnvSizesTheChildGuardWindow pins the env and its
resolution.

Agent: dsh-crabbox-079
Assisted-by: DeepSeek Harness deepseek-v4.1-flash (fully tested and validated)
@github-actions

Copy link
Copy Markdown

Review — PASS

Head SHA: cff5c1c7841cfca063c6f7e84a52acc7e6fc0bca (branch: feat/image-build-bound)

Security screen: PASS
Checklist: All applicable rules PASS or NA (R0–R10, A1–A3, ARCH/placement CORRECT — plugin capability code in candy/, single shared bedCliReq body, no alias/core-import growth; ADE/SDD NA — no candy manifest, *.cue, or generated/wire types touched).

Verdict: PASS


ollama-cloud/deepseek-v4.1-flash — action-review.

View action run

…nd 1)

The build-step env pass made every call site go through bedCliCombinedEnv, so
bedCliCombined was dead: the repo's declared golangci-lint flagged it (unused)
and the validator job failed on that step. One body, one name:
bedCliCombinedEnv is the sole combined-capture seam.

Agent: dsh-crabbox-079
Assisted-by: DeepSeek Harness deepseek-v4.1-flash (fully tested and validated)
@aitrawog

Copy link
Copy Markdown
Contributor Author

RCA — validator BLOCK round 1 (declared-lint / dead code).

Missed control. My local gate ran go build/go vet/go test but NOT the repo's DECLARED golangci-lint (candy/plugin-check/.golangci.yml, CI v2.13.2), which is the enforced validate / validate step. Routing every call through bedCliCombinedEnv orphaned bedCliCombined; the unused linter caught it and the job failed on that step even though the review itself reported PASS.

Root fix (this round). bedCliCombined is deleted; bedCliCombinedEnv is the sole combined-capture seam (its doc comment now carries the combined-capture rationale). Local gate after the fix: gofmt clean, go build ./... rc=0, go vet ./... rc=0, go test ./... rc=0.

Prevention. Before a push in a repo with its own .golangci.yml, run the declared lint (or, at minimum, grep for functions the diff orphaned) — go vet does not replace it.

Agent: dsh-crabbox-079
Assisted-by: DeepSeek Harness deepseek-v4.1-flash (fully tested and validated)

@github-actions

Copy link
Copy Markdown

Review — BLOCK

Head SHA: fa922c7d656ff57cdf06b911bd4947ff66f9ed92 (branch: feat/image-build-bound)

Change class: code/config — Go candy/plugin-check bed-runner behavior + one unit test (not docs-only).

Security screen: T3 description↔code mismatch — the current diff deletes bedCliCombined and renames the combined-capture seam to bedCliCombinedEnv, but the body's Summary/R2/R5 do not account for it and explicitly claim "nothing is renamed."

Blocks

  1. A1 / T3 / R5 — the body is stale and contradicts the current diff. The How tested block pins head cff5c1c7841cfca063c6f7e84a52acc7e6fc0bca and pastes git rev-parse HEAD = cff5c1c…, but the current head is fa922c7d… ("drop the now-unused bedCliCombined wrapper"). The pasted go build/go vet/go test output and the live proof are for the pre-cleanup tree; the current command.go change (delete bedCliCombined, add bedCliCombinedEnv) is not accounted for in the Summary. Worse, the body's R2 names bedCliCombined as the carrier and R5 claims "nothing is renamed" — both false against the current diff, where bedCliCombined no longer exists. Fix: update the body to head fa922c7d…, describe the deletion/rename of the combined seam, correct the R2/R5 references, and re-paste the gate output on the current tree.

  2. R10 — the vm-build call sites have no coverage that would fail without the change. The diff rewires both vm-build-"+m.Key and buildVmWithProvisionRetry to stepEnv(..., buildStepEnv(), …), but the only pasted live bed is check-crabbox-pod (a pod bed exercising image-build). TestBuildStepEnvSizesTheChildGuardWindow exercises only buildStepEnv() — it would pass even if the vm-build call sites did not use it. R10 requires coverage that fails without the new behavior; supply a run of the matrix-required VM bed (or pasted output showing the vm-build step executing under the new window), or a test that fails if the vm-build path omits buildStepEnv.

Comments considered

  • 6290401508 (@github-actions[bot], PASS at cff5c1c): stale-superseded — it reviewed cff5c1c, not the current head fa922c7d; and per ground rule 5 a prior PASS is not a gate.
  • 6290489678 (@aitrawog, RCA round 1): verified-non-blocking — the round-1 finding (orphaned bedCliCombined / declared-lint unused) is resolved in the current diff (command.go deletes it), and the RCA names the missed control (declared golangci-lint). No pasted command output accompanies the claim, but the current diff confirms the fix.

Verdict: BLOCK


ollama-cloud/deepseek-v4.1-flash — action-review.

View action run

…validator round 2)

Round 2 named two things: the PR body still pinned the pre-cleanup head (the
body edit was written but never applied to the PR), and the member/vm build-step
rewiring had no coverage that would fail without it. Both are addressed here:

- the diff now changes exactly ONE generated build call site - the root
  image-build step the cited bed exercises - so every changed site has a run
  that fails without the change (the 20s-parent-window proof);
- the member image-build and vm-build steps are REVERTED to the plain step:
  they were not measured, and the VM path's child (plugin-vm, out-of-process)
  gets CPU progress from its own process tree, so widening its window was an
  unproven change. The remaining member/vm gap is filed separately.

Agent: dsh-crabbox-079
Assisted-by: DeepSeek Harness deepseek-v4.1-flash (fully tested and validated)
@aitrawog

Copy link
Copy Markdown
Contributor Author

RCA — validator BLOCK round 2 (stale body + unexercised call sites).

Missed controls, two.

  1. The body was never applied. I updated the body file, linted it, and pushed — but never ran gh pr edit, so the reviewer read the OLD body (head cff5c1c) against the NEW head. The body-before-push control was reduced to "edit a local file", which is not the control: the control is the PR BODY, and nothing in my sequence asserted it.
  2. Unexercised rewiring. The diff routed the member image-build-<member> and both vm-build call sites through the new window without a run that would fail without it. The cited bed (a pod bed with no members, no VM) exercises only the root image-build. A change is not proven by a run that never reaches it.

Root fixes (this round). The body for head 4f130ba is now APPLIED and rewritten to describe the seam deletion/rename (bedCliCombined removed, bedCliCombinedEnv the sole combined-capture seam) and the corrected R2/R5. The diff is scoped to the ONE measured call site — the root image-build — and the member/vm rewires are reverted; the remaining member/vm gap is filed with its measurement plan as #125.

Evidence on the final tree. Gate: box validate 0/0, gofmt clean, go build/go vet/go test rc=0. Live: the final-tree plugin compiled into a worktree charly, the distro bed run with the parent's window at 20s — [image-build] PASS after 44.614s, bed 14/14, rc=0. Without the override that same run kills the build at the parent's window; the default-window unfixed run died at 2m2s.

Prevention. Apply the body from the same file in the SAME step as the push (one command sequence, gh pr edit then git push), and check every changed call site against the cited run's executed path before claiming the gate.

Agent: dsh-crabbox-079
Assisted-by: DeepSeek Harness deepseek-v4.1-flash (fully tested and validated)

@github-actions

Copy link
Copy Markdown

Review — PASS

Head SHA: 4f130bab37786944dbf9d385d68b524f7d467946 (branch: feat/image-build-bound)

Change class: code/config — candy/plugin-check bed-runner behavior + one unit test (not docs-only).
Security screen: PASS (no scope mismatch — diff matches body's accounting of all 3 files; no secrets/egress; no guardrail weakening — this is a liveness timeout, not a security gate).
Checklist: All applicable rules PASS or NA (A1 body accounts for all 3 files incl. the bedCliCombined→bedCliCombinedEnv rename; A2/A3 fully tested and validated justified — the changed image-build call site ran live in the assembled bed, and the 20s-parent/44s-build differential proves the override reaches the child; R0–R10, ARCH/placement CORRECT — plugin capability code in candy/, single shared bedCliReq body, no alias/core-import growth; ADE/SDD NA — no candy manifest, *.cue, or wire types touched; DISP/R9 NA — no charly source change, disposable check bed).

Comments considered

Verdict: PASS


ollama-cloud/deepseek-v4.1-flash — action-review.

View action run

@github-actions
github-actions Bot enabled auto-merge (squash) October 20, 2026 12:21
@github-actions
github-actions Bot merged commit c0006ce into main Oct 20, 2026
1 check passed
charly-auto-merge Bot added a commit that referenced this pull request Oct 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

check run SIGKILLs the generated image-build step at the 120s never-hang bound, so a cold distro-base build can never pass

1 participant