Skip to content

ci: move the release-path actions off the Node 20 runtime - #329

Closed
FrameAutomata wants to merge 7 commits into
ci/326-node24-actionsfrom
ci/326-node24-third-party
Closed

ci: move the release-path actions off the Node 20 runtime#329
FrameAutomata wants to merge 7 commits into
ci/326-node24-actionsfrom
ci/326-node24-third-party

Conversation

@FrameAutomata

@FrameAutomata FrameAutomata commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

Part 2 of #326, stacked on #328 (which does the 63 actions/* pins). Merge #328 first; this one retargets to main automatically.

These are the six actions #328 left — all third-party, all on release workflows. After this, every pin in .github/workflows is node24 or composite.

Action Workflow
docker/build-push-action v5 → v7 release-traceway (5 uses)
docker/setup-qemu-action v3 → v4 release-traceway
docker/setup-buildx-action v3 → v4 release-traceway
docker/login-action v3 → v4 release-traceway
cloudflare/wrangler-action v3 → v4 release-website, -docs, -helm
azure/setup-helm v4 → v5 release-helm

⚠️ This needs a dry run — no PR label reaches it

Every workflow touched here is workflow_dispatch/release-triggered, so the ci label validates none of it. Suggested order, cheapest first:

  1. release-helm.yml — dispatch. Exercises setup-helm@v5 and a wrangler-action@v4 Pages deploy, and publishes only a chart index.
  2. release-docs.yml or release-website.yml — dispatch. The second wrangler-action shape (command: deploy, Workers rather than Pages).
  3. release-traceway.yml — the four docker/* bumps together. Biggest blast radius; worth doing last and watching the multi-arch sqlite build specifically.

wrangler-action v4 changes the Wrangler CLI major — pinned here

v4's headline change is that its default Wrangler CLI moves from 3 to 4. That matters more than it looks: neither website/package.json nor docs/package.json depends on wrangler, so there is no project-pinned CLI for the action to pick up — its default is literally what deploys tracewayapp.com and the docs.

Taking v4 unpinned would smuggle a CLI major bump into a change whose entire purpose is a runtime bump, on a production deploy path, in a workflow no PR can test. So all three call sites now pin:

wranglerVersion: "3"

Same CLI as today, new runtime. Wrangler 4 is worth doing — as its own change, with its own dry run. Dropping the pin is all it takes.

Why the other five are inert here

build-push-action v6 turned on build summaries (default on, additive — a summary panel plus an exported build record per build). v7 removed the deprecated DOCKER_BUILD_NO_SUMMARY and DOCKER_BUILD_EXPORT_RETENTION_DAYS envs; neither appears anywhere in the repo. Every input in use — context, file, push, tags, platforms, cache-from, cache-to — is unchanged.

setup-buildx-action v4 removed deprecated inputs and outputs (#464). It's invoked with no inputs, and its outputs can't be referenced — the step has no id.

login-action v4, setup-qemu-action v4 and setup-helm v5 are Node 24 + ESM only. login-action's registry/username/password are unchanged; the other two take no inputs at all.

All runners are GitHub-hosted (ubuntu-latest / ubuntu-24.04), so the ≥ 2.327.1 runner requirement is already met.

🤖 Generated with Claude Code

From a cleanup pass over this diff

The build summary is not free, and the bump turned it on silently. build-push-action v6+ generates a summary and uploads a build-record artifact per build. release-traceway.yml runs five builds, so this bump quietly added five artifacts per release, inheriting the repo-wide retention.

Kept rather than disabled — all five builds write cache-to: type=gha,mode=max, and the summary panel is the only place per-build cache-hit ratios and step timings show up without re-running a 30-minute release. Bounded instead with DOCKER_BUILD_RECORD_RETENTION_DAYS: 7 at the job level: a week is long enough to debug the release that produced them. (Note the env var is the v7 spelling — v7 removed the older DOCKER_BUILD_NO_SUMMARY and DOCKER_BUILD_EXPORT_RETENTION_DAYS names.)

The wranglerVersion comments no longer defer across files. Two of the three said "See release-website.yml" for their rationale, which is a coupling nothing enforces — each of these files is normally read alone in a diff, and rewording or retiring the website comment would silently orphan two pointers. Each site now states its own reason.

The pin now has an owner. Its exit condition was prose in a comment and nothing else, on a production deploy path, in a repo where unowned version state drifting until it becomes an annotation is the entire subject of #326. Filed as #331 and referenced from all three sites, so the deferral survives #326 being closed.

dusanstanojeviccs and others added 7 commits August 28, 2026 13:20
app/controllers/setup_test.go hardcodes SQLite handles for both db.DB and
db.TelemetryDB, then calls migrations.Run. Under -tags telemetry_duckdb that
dispatches to duckdbTrackingDDL, whose `applied_at TIMESTAMP DEFAULT now()`
is invalid SQLite -- the dialect requires a parenthesised expression for a
function default -- so all six tests in the file fail with:

    failed to create schema_migrations table: SQL logic error: near "(": syntax error (1)

Add the missing !telemetry_duckdb clause, matching every sibling file that
hardcodes SQLite handles. dashboard_template_seed_test.go, in this same
package, already carries the three-clause form.

No coverage is lost by excluding rather than adding a tagged DuckDB helper.
transactional_sqlite.go is tagged !transactional_pg, so under
-tags telemetry_duckdb the transactional facade resolves to the same
transactional/sqlite package as the default build; these six tests are
main-DB only and never touch db.TelemetryDB beyond save/restore, so they were
exercising byte-identical code in both builds.

Also widen the DuckDB job's test scope to ./... . Its previous scope skipped
app/controllers, which is why this reached main in the first place. The
comment justifying the narrow scope said three packages fail under this tag;
on c7a5dca exactly one did -- this one -- and with the constraint fixed the
full tree passes.

Verified locally in all three supported combinations:

    go test ./...                                        exit 0
    CGO_ENABLED=1 go test -tags telemetry_duckdb ./...    exit 0
    go test -tags "transactional_pg telemetry_ch" ./...   exit 0

Closes #309

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* perf: let per-group telemetry reads seek instead of rescanning the window

Every "one group, one time window" query filters (project_id, <group>,
recorded_at), but no index covered the time column. SQLite therefore seeks
the (project_id, recorded_at) index and re-filters the group column across
the whole window -- once per group, for every group in range, since the
grouped-list loop paginates in Go only after computing percentiles.

Widen the two group indexes and add the missing tasks one. endpoints and
ai_traces are widenings: (project_id, <group>) stays a prefix, so every
query that used the old index is still covered, and no query on these
tables filters the group column without a time bound.

Measured on a 730MB / 2.5M-row telemetry DB seeded over a 30-day window,
without ANALYZE (no deployment runs it):

                  24h window        7d window
  endpoints    6.38s -> 159ms   46.9s -> 1.29s
  tasks          81ms -> 13ms    736ms -> 117ms
  ai traces     108ms -> 13ms   1.03s -> 111ms

Write cost: the widenings are single-digit percent. The one genuinely new
index roughly halves tasks insert throughput (~120k -> ~64k rows/s), which
is a real relative cost but leaves ample headroom for background-task
volume. Building the indexes adds ~3s to the first boot after upgrade, and
migrations complete well before the listener binds, so it is slower start
rather than downtime.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test: run the real migrations instead of a hand-copied schema

The telemetry test helper carried its own 230-line copy of the schema plus
a private re-implementation of migrations.splitStatements. Landing the
index change above meant hand-editing that copy to match -- which is the
tell that the copy is the problem.

It had already drifted, in the same way, more than once:

  - metric_points still declared idx_metric_points_project_name, which
    migration 0006 DROPPED, and never gained the widened replacement --
    the identical drop-and-widen pattern as 0020
  - session_recordings declared an invented idx_session_recordings_session_id
    on (session_id); the real index is idx_session_recordings_session on
    (session_id, segment_index)
  - check_results was absent entirely, though its repository exists
  - idx_exceptions_session, idx_log_records_project_trace,
    idx_log_records_project_service and idx_ai_traces_project_trace_name
    were all missing

dbtest.SetupSQLite already does this properly and is used by five other
test packages. Route the helper through it: the copy and the duplicated
splitStatements go away, the suite exercises the real 20-migration schema,
and the 105 setupTestDB call sites are untouched. Package runtime goes
0.30s -> 2.19s, the cost of applying the real migration set per setup.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
`GetTraceNameStats` on the SQLite telemetry backend swallowed the error
from its percentile query with `return stats, nil`. On any failure --
context deadline on a large trace_name, a locked telemetry DB, a scan
type error -- the caller got a struct with a real Count, AvgDuration and
token totals next to MedianDuration and P95Duration of exactly zero, and
nothing was logged or captured.

Zero is a plausible latency, so this reads as a fast trace rather than a
failed read. The DuckDB and ClickHouse backends compute the percentiles
inside the aggregate query and propagate the error, and the SQLite
endpoint and task equivalents already `return nil, err`, so this was the
one path that fabricated a value.

The route treats stats as best-effort -- it nils them and still answers
200 with the trace list -- so propagating the error does not turn this
into a 500. It swaps a misleading zero for an absent stats block, and
the discard in the controller now reports through CaptureException per
the non-stopping-error convention rather than vanishing.

No regression test: both queries in this function read the same table,
so there is no way to fail only the percentile read from a test without
injecting a fake executor, which the repositories do not support.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
`sortEndpointStats` produced its descending order by negating the
ascending comparison. For two rows that tie on the sort key that returns
true for both (i, j) and (j, i), which is not the strict weak ordering
sort.Slice documents a requirement for, so the result is formally
unspecified. Descending is now the same comparison with the operands
swapped.

This is hygiene, not a bug fix. I could not make the old comparator
misbehave: 2400 trials over slices of 8 to 20000 elements, 1 to 100
distinct values and three input patterns produced zero misordered
results and zero lost elements, so Go's pdqsort tolerates the `>=`
comparator in practice today. What the change buys is not depending on
that tolerance.

No test, because none can distinguish the two versions -- any behaviour
a test could asserts holds for the old comparator too, which is what the
probe above measured. The sibling sort in task.repository.go already
used the strict form; this brings the two endpoint repositories in line
with it.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
The six actions #326 left after the actions/* sweep, all third-party and
all on release workflows:

  docker/build-push-action   v5 -> v7   (release-traceway, 5 uses)
  docker/setup-qemu-action   v3 -> v4   (release-traceway)
  docker/setup-buildx-action v3 -> v4   (release-traceway)
  docker/login-action        v3 -> v4   (release-traceway)
  cloudflare/wrangler-action v3 -> v4   (release-website, -docs, -helm)
  azure/setup-helm           v4 -> v5   (release-helm)

Every pin in .github/workflows is node24 or composite after this.

No PR label exercises any of these, so this wants a workflow_dispatch dry
run before merging -- see the PR description for which and in what order.

wrangler-action v4 changed its default Wrangler CLI from 3 to 4. Neither
website/package.json nor docs/package.json depends on wrangler, so the
action's default is what actually deploys tracewayapp.com and the docs:
taking v4 unpinned would carry a CLI major bump into a production deploy
inside a runtime change. `wranglerVersion: "3"` holds the CLI where it is
so this change moves one thing. Wrangler 4 is worth doing, deliberately
and on its own.

The other five are inert for how they are called here:

build-push-action v6 turned on build summaries (default on, additive) and
v7 dropped the DOCKER_BUILD_NO_SUMMARY and DOCKER_BUILD_EXPORT_RETENTION_DAYS
envs -- neither is set anywhere in the repo. The inputs in use (context,
file, push, tags, platforms, cache-from, cache-to) are unchanged.

setup-buildx-action v4 removed deprecated inputs and outputs. It is
invoked with no inputs and its outputs are not referenced -- the step
has no id.

login-action v4, setup-qemu-action v4 and setup-helm v5 are Node 24 plus
ESM only. login-action's registry/username/password are unchanged, and
the other two take no inputs.

Refs #326.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…in an owner

build-push-action v6+ generates a build summary and uploads a build
record artifact per build. release-traceway.yml runs five of them, so the
bump silently added five artifacts per release inheriting the repo-wide
retention. The summary itself earns its keep -- all five builds write
cache-to: type=gha,mode=max and it is the only place per-build cache-hit
ratios are visible without re-running a release -- so this caps the
record retention at a week rather than disabling the feature.

The three wranglerVersion comments deferred to release-website.yml for
their rationale, which is a coupling nothing enforces: each of these
files is normally read alone in a diff, and rewording or retiring the
website comment would silently orphan two pointers. Each site now states
its own reason.

The pin's exit condition was also prose and nothing else, on a production
deploy path, in a repo where unowned version state drifting until it
becomes an annotation is the whole subject of #326. Filed as #331 and
referenced from all three sites. #330 covers the absent drift detection
that let #326 happen twice in one day.

Refs #326.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@FrameAutomata
FrameAutomata force-pushed the ci/326-node24-third-party branch from 8de17dd to 245cc4f Compare August 28, 2026 22:26
@FrameAutomata

FrameAutomata commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator Author

Reopening against main as #344 — GitHub refuses to retarget a PR it considers part of a stack (Cannot change the base branch because the pull request is part of a stack), so closing is the only way off #328's base. Same branch, same two commits, rebased onto main; no content is lost.

Also corrected the validation section, which told reviewers to dry-run these with workflow_dispatch. There is no dry run here — dispatching release-docs/release-website deploys to production Cloudflare and release-helm overwrites the live chart index.

FrameAutomata added a commit that referenced this pull request Aug 28, 2026
Nothing in this repo watched action versions: no dependabot.yml, no
renovate.json, and the strings appear nowhere. That is how the Node 20
runtime migration (#326) was found twice in one day, both times because a
human read a run annotation. #328/#329 bought distance from that deadline
but no detection, so the next major lands exactly as silently.

Adopt Dependabot for the github-actions ecosystem only. npm and Go modules
are left unwatched on purpose -- backend/ and cli/ already run govulncheck
daily, and those ecosystems would bury the signal this file exists for.

Two groups, split the same way #328/#329 split by hand, on what a major
means rather than on who ships it: actions/* majors are normally runtime
bumps, wide and mechanical and reviewable as one sweep, while a third-party
major can change behaviour -- wrangler-action v4 silently moved its default
Wrangler CLI from 3 to 4, which is the whole of #331.

The PRs are deliberately not auto-labelled `ci`. That label is the
collaborator trust gate that makes PR CI opt-in, and an unattended bot PR
proposing an action version nobody has read should not trip it; a reviewer
applies it after looking, as for any human PR. Worth being clear-eyed that
this is mostly detection and not validation: `ci` reaches only backend*.yml
and cli*.yml, while most pins sit on release-*.yml, benchmark-*.yml and
traceway-autofix.yml, where validating a bump still means a
workflow_dispatch dry run. Detection is the half that was missing.

Settle the two Node questions in the same pass, since they are the same
discipline. release-docs.yml and release-website.yml pinned
`node-version: "latest"`, which floats two production deploys onto whatever
Node shipped that morning, up to and including a non-LTS Current release --
and neither docs/package.json nor website/package.json declares an engines
floor to catch it. Both now pin 22, the major already used by
release-traceway.yml, benchmark-processor.yml and the node:22-alpine
Dockerfiles, matching frontend/package.json's >=22. Whether Traceway moves
to Node 24 is now one decision made once rather than one made silently on
two deploy paths.

The source-map upload recipe was the last surviving `node-version: 20` in
the repo, recommending to readers a major this repo uses nowhere while
#328 had just bumped the action pins around it to the Node 24 majors. It
recommends 22 now, the same version the shipped Dockerfiles run.

Closes #330

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

2 participants