Skip to content

upstream-drift

upstream-drift #52

name: upstream-drift
# THE DAILY QUESTION: did a new Claude Code break clode?
#
# clode reads a bundle it does not own, so upstream can break us without breaking
# itself — and the failure is silent by nature: an anchor that no longer matches
# means a hook is NOT applied, while everything still builds and PONGs. 2.1.210
# reshaped the pkg-manager autoupdater site (2.1.207 was fine), the redirect
# stopped applying, and nobody noticed for weeks.
#
# WHY A SEPARATE JOB, when ci.yml also installs the provider: attribution. This
# asks a DIFFERENT question than a push does. A push asks "did my diff break
# clode?"; this asks "did Anthropic's release break clode?" Collapsing them into
# one red light is how an upstream break lands on an innocent commit and someone
# spends a session bisecting job conclusions to discover it was never their code.
# Red HERE means upstream moved. It says so in the job name.
#
# Same doctrine as guest-versions.yml (the weekly sweep that backstops implicit
# pins) — scripts/tjs-legs.mjs states it: Renovate owns explicit pins, a scheduled
# sweep backstops the implicit ones. The provider is an implicit pin (every job
# installs latest), so this is that backstop.
#
# START SMALL, FLESH OUT (user, 2026-07-17): one check today — every hook anchor is
# present. Add checks to scripts/upstream-drift-check.mjs as we find more ways
# upstream can break us. Deliberately NOT built on `inspect --strict`: that gates on
# every unimplemented Bun.* member and is chronically red on versions that work
# fine, and a job that is always red teaches people to ignore it.
on:
schedule:
- cron: '23 5 * * *' # daily 05:23 UTC (off the hour; nothing else runs then)
workflow_dispatch: {}
permissions:
contents: read
jobs:
anchors:
runs-on: ubuntu-latest
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v7.0.1
- uses: actions/setup-node@v7.0.0
with:
node-version-file: .tool-versions
# `next`, deliberately unpinned, and deliberately NOT `latest`.
#
# We watched both for one day and it settled the question: `next` is the leading
# channel. 2.1.243 and 2.1.245 were both on `next` before `latest`, and on
# 2026-08-25 `latest` moved from 2.1.241 to 2.1.245 — the code-split format that
# `clode build` could not read — with no warning to anyone watching `latest` alone.
# Watching the newer channel is the only version of this job that can tell us
# something BEFORE every user is already broken.
#
# One channel is enough (user, 2026-08-25). When the two are the same version this
# costs nothing; when they differ, `next` is the one worth knowing about.
#
# This job does NOT read UPSTREAM_PIN (the 2.1.251 pin ci.yml/naude-cross.yml/
# build-leg install to dodge the 2.1.257 SCC-merge break — see that file). Pinning
# here would blind the early-warning system at exactly the moment we are choosing
# to lag on purpose: this job's only reason to exist is to notice upstream moving
# WHILE everything else holds still. Leave it unpinned.
- name: Install the `next` Claude Code
run: npm i -g @anthropic-ai/claude-code@next 2>&1 | tail -3
- name: Every clode hook anchor still matches the newest bundle
run: |
set -euo pipefail
VER="$(npm view @anthropic-ai/claude-code@next version)"
echo "upstream Claude Code (next): $VER"
PROV="$(node scripts/find-provider.mjs)"
# A missing provider must fail, not skip: "we could not check" is not
# "nothing changed", and this job's only product is a trustworthy answer.
if [ -z "$PROV" ]; then echo "ERROR: no Bun provider found under @anthropic-ai/claude-code" >&2; exit 1; fi
echo "provider: $PROV"
echo "$VER" > "$RUNNER_TEMP/upstream-version"
node scripts/upstream-drift-check.mjs "$PROV"
# WHEN IT BREAKS, SAY WHAT UPSTREAM SAID. Twice now upstream has broken
# `clode build` with no change in this repo, and both times a public changelog
# already named the area while we byte-diffed binaries for hours (2.1.238's
# os.constants.errno read, and 2.1.243's switch to Bun code splitting). The
# information was never hard to get; nothing connected "the daily check went
# red" to "go read the release notes".
#
# Runs only on failure, and cannot turn a red into a green: the step above has
# already failed by the time this runs, and the tool exits 0 even when it cannot
# reach the network, so it never becomes a second thing to debug.
#
# LIMITATION, stated rather than hidden: we do not persist the last version this
# job was green on, so there is no true "since we last passed" range. Passing a
# version that is not in the changelog makes the tool print the newest few
# sections, which is the useful 90% with no state to keep in sync. If we ever
# persist a last-green version, pass it as --from and delete this comment.
- name: What did upstream say? (context only — a LEAD, never a cause)
if: failure()
run: |
VER="$(cat "$RUNNER_TEMP/upstream-version" 2>/dev/null || true)"
node scripts/upstream-release-notes.mjs --from "0.0.0-no-last-green-recorded" ${VER:+--to "$VER"}
# THE SECOND DAILY QUESTION: does a quaude built from the newest bundle still BOOT?
#
# The anchors job above asks whether our hook sites still match. It cannot see the
# other way upstream breaks us: the bundle starts READING a node API the shim does
# not implement. 2.1.238 began evaluating
# new Map(Object.entries(require("os").constants.errno))
# at module init; os.constants carried only .signals, so Object.entries(undefined)
# threw and EVERY quaude built against 2.1.238 was dead on arrival. The anchors
# check was green throughout — nothing was mis-anchored, the shim was just missing
# a table.
#
# Nothing ran the build smoke on a schedule, so the only way to find this was for a
# human to dispatch a build; it surfaced by accident during unrelated pack work,
# and presented as CI infrastructure noise. One cheap leg daily converts that into
# a dated, attributed red light within a day of an upstream release.
#
# A FOURTH DAILY QUESTION, and the cheapest: has the Haiku blocker lifted?
#
# haiku-x64 fails because cross-platform-actions ships only an r1beta5 guest image and
# HaikuPorts moved to the current release, so the guest cannot install packages at all.
# Not our bug, and not something a build can fix. Running that leg on every push spent a
# runner slot to re-derive an answer we had already written down, while the question we
# actually care about — has a beta6 image appeared? — went unasked.
#
# This asks it in about a second. Green means "nothing we could do about Haiku today",
# NOT "Haiku is fine": the leg is deliberately out of the push matrix and publishes
# nothing (see BACKLOG). Red means an action just became possible.
# linux-x64-musl deliberately: an alpine container, no qemu, and the engine comes
# from the same PINS+patches cache the push legs use, so the marginal cost is the
# fuse and the smoke — not an engine build.
#
# Red HERE means the newest bundle no longer boots under the shim. Same attribution
# doctrine as the anchors job: it is upstream that moved, not the last commit.
boots:
uses: ./.github/workflows/tjs-legs.yml
permissions:
contents: read
with:
tier: release
only: linux-x64-musl