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.

Sync: repeat only the warnings a deploy raised, not the ones its script text contains (#167)

A step's log starts with its own script, so the sync's filter, which took `::warning::` anywhere
in a line, repeated `echo "::warning::..."` from the sign-in page check as if the deploy had
raised it: #164's sync warned that /security/report had no Turnstile and /auth/access had no
Access, while the same log said the page shows Turnstile. The filter is now its own script,
deploy-log-notes.sh, which counts a warning only where the runner wrote it (after the timestamp)
and also repeats what the deploy found at /auth/access.


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

Co-authored-by: Claude <noreply@anthropic.com>
Canberk Demirci authored · GitHub committed signed1 parent 8fb3f55commit 3cece21da38ca472b140231c85c672482c49f882Browse files
3 files changed+80−6
addedapps/diffinery/test/unit/deploy-log-notes.test.ts+64−0View file
@@ -1,0 +1,64 @@
1+import { spawnSync } from "node:child_process";
2+import { describe, expect, it } from "vitest";
3+
4+/**
5+ * scripts/deploy-log-notes.sh: what the GitHub sync repeats from a Diffinery Deploy job's log. A
6+ * step's log begins with its script, whose `echo "::warning::..."` lines are not warnings.
7+ */
8+
9+const script = new URL("../../../../scripts/deploy-log-notes.sh", import.meta.url).pathname;
10+const notes = (log: string) => {
11+ const r = spawnSync("bash", [script], { input: log, encoding: "utf8" });
12+ expect(r.status).toBe(0);
13+ return r.stdout.split("\n").filter(Boolean);
14+};
15+
16+// The "Check sign-in pages" step of a deploy where Access is set up and Turnstile works.
17+const step = (lines: string[]) =>
18+ [
19+ "2026-10-14T09:14:34.280Z ##[group]Run loc=$(curl -sS -o /dev/null -w '%{http_code} %{redirect_url}' https://$DIFFINERY_DOMAIN/auth/access)",
20+ "2026-10-14T09:14:34.281Z \u001b[36;1mloc=$(curl -sS -o /dev/null -w '%{http_code} %{redirect_url}' https://$DIFFINERY_DOMAIN/auth/access)\u001b[0m",
21+ '2026-10-14T09:14:34.281Z \u001b[36;1mecho "/auth/access -> $loc"\u001b[0m',
22+ '2026-10-14T09:14:34.282Z [[ "$loc" == 302\\ https://*.cloudflareaccess.com/* ]] || echo "::warning::/auth/access is not behind Cloudflare Access yet, so email recovery and first admin sign-in will not work"',
23+ "2026-10-14T09:14:34.286Z report=$(curl -sS -w '\\n%{http_code}' https://$DIFFINERY_DOMAIN/security/report)",
24+ '2026-10-14T09:14:34.286Z echo "/security/report shows Turnstile to people who aren\'t signed in"',
25+ '2026-10-14T09:14:34.286Z elif grep -q \'has no Turnstile configured\' <<<"$report"; then echo "::warning::/security/report has no Turnstile, so only people who are signed in can send security reports: the Worker lacks TURNSTILE_SITE_KEY or TURNSTILE_SECRET"',
26+ "2026-10-14T09:14:34.287Z ##[endgroup]",
27+ ...lines,
28+ ].join("\n");
29+
30+describe("deploy log notes", () => {
31+ it("repeats what the deploy printed, not the warnings its scripts could print", () => {
32+ const log = [
33+ '2026-10-14T09:14:07.959Z Turnstile widget "diffinery diffinery.com" exists: 0x4AAAAAAFWX2OQHwh_jT9_h',
34+ "2026-10-14T09:14:08.227Z TURNSTILE_SECRET goes with site key 0x4AAAAAAFWX2OQHwh_jT9_h",
35+ step([
36+ "2026-10-14T09:14:34.500Z /auth/access -> 302 https://team.cloudflareaccess.com/cdn-cgi/access/login",
37+ "2026-10-14T09:14:34.730Z /security/report shows Turnstile to people who aren't signed in",
38+ ]),
39+ ].join("\n");
40+ const out = notes(log);
41+ expect(out.filter((l) => l.startsWith("::warning::"))).toEqual([]);
42+ expect(out).toEqual([
43+ ' deploy: 2026-10-14T09:14:07.959Z Turnstile widget "diffinery diffinery.com" exists: 0x4AAAAAAFWX2OQHwh_jT9_h',
44+ " deploy: 2026-10-14T09:14:08.227Z TURNSTILE_SECRET goes with site key 0x4AAAAAAFWX2OQHwh_jT9_h",
45+ " deploy: 2026-10-14T09:14:34.500Z /auth/access -> 302 https://team.cloudflareaccess.com/cdn-cgi/access/login",
46+ " deploy: 2026-10-14T09:14:34.730Z /security/report shows Turnstile to people who aren't signed in",
47+ ]);
48+ });
49+
50+ it("repeats the warnings the deploy raised", () => {
51+ const out = notes(
52+ step([
53+ "2026-10-14T09:14:34.500Z /auth/access -> 200 ",
54+ "2026-10-14T09:14:34.501Z ##[warning]/auth/access is not behind Cloudflare Access yet, so email recovery and first admin sign-in will not work",
55+ "2026-10-14T09:14:34.730Z ::warning::/security/report has no Turnstile: it answered 503 without the form",
56+ ]),
57+ );
58+ expect(out).toEqual([
59+ "::warning::Diffinery Deploy: /auth/access is not behind Cloudflare Access yet, so email recovery and first admin sign-in will not work",
60+ "::warning::Diffinery Deploy: /security/report has no Turnstile: it answered 503 without the form",
61+ " deploy: 2026-10-14T09:14:34.500Z /auth/access -> 200 ",
62+ ]);
63+ });
64+});
addedscripts/deploy-log-notes.sh+15−0View file
@@ -1,0 +1,15 @@
1+#!/usr/bin/env bash
2+# Reads a Deploy job's log on stdin and prints what the sync repeats on GitHub: the warnings the
3+# deploy raised, as workflow commands, and its lines about Turnstile and Cloudflare Access. A
4+# step's log starts with its script, so a line only counts where the runner wrote it (after the
5+# timestamp), never where the script's text says `echo "::warning::..."`.
6+set -uo pipefail
7+log=$(cat)
8+ts='^([0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9:.]+Z )?'
9+sed -nE "s/${ts}(##\[warning\]|::warning[^:]*::)//p" <<<"$log" | head -n 40 | while IFS= read -r w; do
10+ echo "::warning::Diffinery Deploy: $w"
11+done
12+# Site keys are public; the secret key is never printed.
13+grep -E "${ts}(Turnstile widget|TURNSTILE_SECRET|site key:|/security/report|/auth/access)" <<<"$log" \
14+ | grep -vE "${ts}(##\[|::)|\\\$|echo " | head -n 10 | sed 's/^/ deploy: /'
15+exit 0
modifiedscripts/diffinery-wait-runs.sh+1−6View file
@@ -56,12 +56,7 @@
5656 # bootstrap needs, say): repeat them here, where they can be read without access to Diffinery.
5757 if [ "$(jq -r .name <<<"$run")" = Deploy ]; then
5858 for job in $(jq -r '.jobs[] | select(.conclusion != "skipped") | .id' <<<"$jobs"); do
59- log=$(api "/actions/jobs/$job/logs" 2>/dev/null) || true
60- sed -nE 's/^.*(##\[warning\]|::warning[^:]*::)//p' <<<"$log" | head -n 40 | while IFS= read -r w; do
61- echo "::warning::Diffinery Deploy: $w"
62- done || true
63- # What the deploy did about Turnstile (site keys are public; the secret key is never printed).
64- grep -E 'Turnstile widget|TURNSTILE_SECRET|site key:|/security/report' <<<"$log" | grep -v 'warning' | head -n 10 | sed 's/^/ deploy: /' || true
59+ api "/actions/jobs/$job/logs" 2>/dev/null | "$(dirname "$0")/deploy-log-notes.sh" || true
6560 done
6661 fi
6762 # A skipped run (every job's `if` was false) is fine; a skipped Deploy after a failed CI