Diffinery

codician/diffinerypublic

Open-source, agent-first GitHub and GitHub Actions alternative that runs on your own Cloudflare account: git hosting, pull requests, code review and drop-in GitHub Actions YAML on Workers, Durable Objects, R2 and Containers.

Jobs report how often they went back in line; the Live test shows it (#150)

The jobs API returns placementTries: the times a hosted job was placed again
before a runner took it (no machine, or a runner on a replaced image turned away).
The Live test prints it for the probe and for each job of its main run, so a
deploy's rollout shows whether jobs were turned away, and the probe now waits up
to 10 minutes for a run whose jobs were started again.


Claude-Session: https://claude.ai/code/session_01Ruyx3Qr4jYrZEpPi466deb

Co-authored-by: Claude <noreply@anthropic.com>
Canberk Demirci authored · GitHub committed signed1 parent 8e39f86commit 617abd08596500e00e8f6e40954924737b8a1b96Browse files
3 files changed+11−5
modified.github/workflows/live-test.yml+7−4View file
@@ -200,7 +200,8 @@
200200 before=$(api GET "/repos/$OWNER/$REPO/actions/runs?event=workflow_dispatch" | jq '.workflow_runs | length')
201201 api POST "/repos/$OWNER/$REPO/actions/workflows/probe.yml/dispatches" '{"ref":"main"}' >/dev/null
202202 run=""
203- for i in $(seq 1 60); do
203+ # Up to 10 minutes: a job turned away from a replaced image starts again after 30 s, 1, 2 and 4 minutes.
204+ for i in $(seq 1 120); do
204205 runs=$(api GET "/repos/$OWNER/$REPO/actions/runs?event=workflow_dispatch")
205206 if [ "$(jq '.workflow_runs | length' <<<"$runs")" -gt "$before" ]; then
206207 run=$(jq -c '.workflow_runs[0]' <<<"$runs")
@@ -209,8 +210,9 @@
209210 sleep 5
210211 done
211212 [ -n "$run" ] || { echo "::error::the probe workflow didn't start"; exit 1; }
212- images=$(api GET "/repos/$OWNER/$REPO/actions/runs/$(jq -r .id <<<"$run")/jobs" | jq -r '.jobs[] | .runner.image // "no image reported"')
213- echo "probe jobs ran on: $(sort <<<"$images" | uniq -c | sed 's/^ *//' | paste -sd ',')"
213+ probe=$(api GET "/repos/$OWNER/$REPO/actions/runs/$(jq -r .id <<<"$run")/jobs")
214+ images=$(jq -r '.jobs[] | .runner.image // "no image reported"' <<<"$probe")
215+ echo "probe jobs ran on: $(sort <<<"$images" | uniq -c | sed 's/^ *//' | paste -sd ','), started again $(jq '[.jobs[].placementTries // 0] | add // 0' <<<"$probe") time(s) in all"
214216 [ "$(grep -cxF "$expected" <<<"$images" || true)" = 3 ] && { echo "jobs start on $expected"; exit 0; }
215217 if [ "$(date +%s)" -ge "$deadline" ]; then
216218 echo "::error::15 minutes after the deploy, jobs still start on a runner image other than the deployed $expected"
@@ -406,7 +408,8 @@
406408 echo "$run" | jq '{id, status, conclusion}'
407409 id=$(jq -r .id <<<"$run")
408410 jobs=$(api "/repos/$OWNER/$REPO/actions/runs/$id/jobs")
409- jq -r '.jobs[] | " \(.name): \(.conclusion)\(if .failure then " - " + (.failure.message // "") else "" end)\(try " (\((.completed_at | fromdateiso8601) - (.started_at | fromdateiso8601))s)" catch "")"' <<<"$jobs"
411+ # placementTries: times a job went back in line (no machine, or a runner on a replaced image turned away).
412+ jq -r '.jobs[] | " \(.name): \(.conclusion)\(if .failure then " - " + (.failure.message // "") else "" end)\(try " (\((.completed_at | fromdateiso8601) - (.started_at | fromdateiso8601))s)" catch "")\(if (.placementTries // 0) > 0 then ", started again \(.placementTries) time(s)" else "" end)"' <<<"$jobs"
410413 if [ "$(jq -r .conclusion <<<"$run")" != success ]; then
411414 for job in $(jq -r '.jobs[] | select(.conclusion == "failure" or .conclusion == "timed_out") | .id' <<<"$jobs"); do
412415 echo "::group::Last log lines of job $job"
modifiedapps/diffinery/docs/actions/runners.md+1−1View file
@@ -147,7 +147,7 @@
147147
148148 If the container stops unexpectedly (for example it runs out of memory), the job fails with `The runner stopped unexpectedly (exit code N).` A container that stops before its runner fetched the job is replaced instead, since no step ran.
149149
150-The log's first lines name where the job ran, for example `Runner: image diffinery-runner:96dbbe31790f, in fra06, EEUR, container 6f1c…`. The jobs API returns the same as `runner` (`image`, `location`, `instance`), next to `runner_name`. `GET /meta` names the image the last deploy set as `runner_image`. While a deploy's rollout is updating instances, a new container can still start on the previous image. Diffinery turns that runner away before it runs anything and starts the job again on another container (after 30 s, then 1, 2 and 4 minutes). After four tries, the job runs on whatever image it got rather than wait for a rollout that has stalled.
150+The log's first lines name where the job ran, for example `Runner: image diffinery-runner:96dbbe31790f, in fra06, EEUR, container 6f1c…`. The jobs API returns the same as `runner` (`image`, `location`, `instance`), next to `runner_name`. `GET /meta` names the image the last deploy set as `runner_image`. While a deploy's rollout is updating instances, a new container can still start on the previous image. Diffinery turns that runner away before it runs anything and starts the job again on another container (after 30 s, then 1, 2 and 4 minutes). After four tries, the job runs on whatever image it got rather than wait for a rollout that has stalled. The jobs API's `placementTries` counts the times a job went back in line before a runner took it.
151151
152152 ### Windows and macOS
153153
modifiedapps/diffinery/src/repo/actions.ts+3−0View file
@@ -231,6 +231,8 @@
231231 /** The runner that took the job: a self-hosted runner's name, or the hosted container's name. */
232232 runnerName: string | null;
233233 runner: JobRunnerInfo | null;
234+ /** Times a hosted job went back in line before a runner took it (no machine, or a runner on a replaced image). */
235+ placementTries: number;
234236 };
235237
236238 const toRun = (r: RunRow): RunInfo => ({
@@ -269,6 +271,7 @@
269271 summary: j.summary,
270272 runnerName: j.container?.replace(/^self-hosted:/, "") ?? null,
271273 runner: j.runner_info ? (JSON.parse(j.runner_info) as JobRunnerInfo) : null,
274+ placementTries: j.placement_tries ?? 0,
272275 });
273276
274277 /**