docs: plan the pnpm release-age retry deferred from go-udap - #20
Closed
robinbowes wants to merge 7 commits into
Closed
robinbowes wants to merge 7 commits into
robinbowes wants to merge 7 commits into
Conversation
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The dependency-PR-resilience and stuck-PR-recovery work replaced state read from six sites over seven stores with one resolved `PrState` snapshot per dispatch plus pure decisions over it. The spec documents that well; the docs site did not. The four workflow pages each restated a fragment — `requires-human` five times across two pages, the run lock three times, the dispatch gate twice — none of them named the snapshot, showed the ordering of the guards, or drew anything, and nowhere did the site say why a team should care. New `/docs/pr-state` page in a Concepts nav section above Workflows. It leads with the team problem rather than the refactor: a pull request is the unit of a release workflow, several actors work it at once, and without one agreed answer you get duplicate pushes, repeated escalation comments, reviews of a tree being rewritten, and PRs that stop silently. Then the mechanism, anchored on a real run's PR-state panel. Five hand-authored SVG diagrams (no mermaid on this site, and docs pages are .astro rather than markdown): the choke point, the ten-guard ladder colour-coded by exit kind, the fix attempt machine, the review resolver's three verdicts and the check each leaves, and the dependency lane with both daily crons. The ladder is generated from a frontmatter array so it cannot drift out of order. `generate-md.mjs` strips `svg` from the .md mirrors, so every diagram has a prose or table equivalent beside it — the SVG is never the sole carrier of a fact. The diagrams hold a `min-width` floor so a phone scrolls the card instead of scaling 8px labels into illegibility. The four workflow pages lose the duplicated prose and link here instead. Their "the four PR-scoped workflows" wording is now false — the set is derived from each workflow's own `pr_scoped: true` key, so an overlay fork keeps the gate — and is softened to name the packaged four as an example. Also fixes a pre-existing break in the prev/next chain: Configuration's "Previous" pointed at Slack integration, skipping the whole Workflows section. Docs-only. No `apps/server/spec` changes — the spec is already current here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A GitHub App is installed per ACCOUNT, and each installation mints its own
tokens. The harness threaded one statically-configured
`GITHUB_APP_INSTALLATION_ID` into every mint and every App-authed Octokit, so
everything against a second account failed: token mints 422'd ("at least one
repository ... is not accessible to the parent installation"), and every
harness-side call — comments, reactions, check runs, `.lastlight/` fetches,
post-review — 404'd.
Installations are now DISCOVERED and resolved per repository owner.
- `InstallationDirectory` (engine/github/installations.ts) is the single
owner→installation authority. Fed by every webhook's `payload.installation`
and by `GET /app/installations` under an App JWT; concurrent misses share one
request and negatives are cached briefly, so a cron fan-out over N repos costs
one call, not N. Suspended installations are withheld from resolution (they
403 every mint) but stay listed, flagged, for the admin surface.
- `GitHubClient` resolves its Octokit per owner, memoized per installation.
Every method already took `owner` first, so ~50 call sites — `DispatchDeps`,
the router, dispatcher, pr-state, review-check, repo-config — are unchanged.
The read-only chat tools get the same treatment; the two `search` tools read
the account from the query's `repo:`/`org:`/`user:` qualifier.
- The per-run mint resolves from `githubAccess.owner`. An owner with no usable
installation fails the phase immediately, naming the ACCOUNT — no sandbox, no
API call.
- Installation repo discovery is keyed by installation id. Those webhooks are
per-account: against one flat set a second org's `created` reset the managed
list to just that org and its `deleted` cleared it entirely. `suspend` /
`unsuspend` are handled too — previously they fell through silently.
- `GITHUB_APP_INSTALLATION_ID` is now OPTIONAL, kept only as the fallback for
when the JWT lookup itself fails, so an existing single-installation
deployment keeps its old behaviour with no .env edit.
- `GET /admin/api/managed-repos` reports every installation (account, id, repo
count, selection, suspended) plus `uninstalledOwners`; Config → Managed repos
renders them and warns when a managedRepos owner has no installation — the
condition is visible before it becomes a failed run.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The installations pane named an account and an id and left you to build the URL by hand — which is the one thing you actually want to do next from every question it raises (change the repo grant, un-suspend, uninstall). The path shape depends on the account type and guessing wrong 404s: an org install lives at `/organizations/<login>/settings/installations/<id>`, a personal one at a viewer-scoped `/settings/installations/<id>`. So `installationSettingsUrl()` builds it server-side from the account type, and returns undefined when that type isn't known yet — a record seeded from a webhook that carried no `account.type` — so the pane renders plain text rather than a link that may not resolve. `note()` now takes `account.type` from the payload and backfills it, so a webhook-learned install is linkable too. The `uninstalledOwners` warning also gets `appInstallUrl` (derived from `botName`, which IS the App slug), so it offers the fix instead of only naming the problem. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The go-udap decision of 2026-07-25 deferred this requirement here and recorded a path for it; the document was never written, and go-udap#187 hit the same failure again on 2026-08-04. pnpm 11 enforces minimumReleaseAge on install --frozen-lockfile, rejecting lockfile entries published inside the window. Dependabot's cooldown gates only direct bump targets, so transitively re-resolved deps still trip it. Nothing is broken and the same lockfile passes once the clock advances. Every retry path today is event-driven, and no event fires when a clock passes. Records that the clear time is computable from the error text (published_at + (runStartedAt - cutoff)), that the action is rerun-failed-jobs with no commit, and that regenerating the lockfile restarts the clock rather than clearing it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
robinbowes
added a commit
to yo61/go-udap
that referenced
this pull request
Aug 4, 2026
The 2026-07-25 decision cited lastlight/docs/plans/dependabot-pnpm-release-age-retry.md as the home of the deferred requirement. That file was never written — lastlight held no reference to minimumReleaseAge at all — which is why go-udap#187 hit the same failure again on 2026-08-04. The plan now exists as a directory, matching lastlight's convention (yo61/lastlight#20). Update the pointer to match. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
robinbowes
added a commit
to yo61/go-udap
that referenced
this pull request
Aug 4, 2026
* ci: remove local Dependabot auto-merge workflow The workflow has never enabled auto-merge on any PR. Its guard tests package-ecosystem == 'npm', but dependabot/fetch-metadata emits the internal resolver name npm_and_yarn. Run 30897608171 shows update-type and directory both matching and only the ecosystem comparison failing; timeline events for #173, #178, #180, #184, #186 and #187 confirm no auto-merge was ever enabled by it. The failure was invisible because the check reports SUCCESS on every PR — the job succeeds and only the step inside is skipped by its own if:. Last Light's dependabot-pr-merge workflow already owns the classify -> label -> auto-merge decision across managed repos and does enable auto-merge here, so removing this changes no behaviour. dependabot- automerge is not a required context, so the ruleset is unaffected. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: record Last Light signed-branch-update constraint main enforces required_signatures, evaluated over every commit reachable in a PR. Last Light builds branch-update merges with git in a sandbox and configures no commit signing, so those commits arrive unsigned and block the PR they were meant to unblock — as happened to #185. Records that Last Light should use the update-branch API (server-side, web-flow signed) or rerun-failed-jobs, rather than local git merge. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: point the pnpm-retry decision at the plan that now exists The 2026-07-25 decision cited lastlight/docs/plans/dependabot-pnpm-release-age-retry.md as the home of the deferred requirement. That file was never written — lastlight held no reference to minimumReleaseAge at all — which is why go-udap#187 hit the same failure again on 2026-08-04. The plan now exists as a directory, matching lastlight's convention (yo61/lastlight#20). Update the pointer to match. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: verify update-branch produces a signed commit The decision recorded update-branch's signing behaviour as documented generally but unconfirmed for this endpoint. Running it against #189 and #190 produced merge commits committed by GitHub <noreply@github.com> with verified=true, reason=valid. Also notes that update-branch writes "Merge branch 'main' into X" while a sandbox merge writes "Merge remote-tracking branch 'origin/main' into X", so the two are distinguishable in history. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Author
|
Superseded by nearform#269, for the same reason as #19: this is a fork, and the work belongs on the upstream tracker rather than as a fork-local plan directory. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
docs/plans/dependabot-pnpm-release-age-retry/README.md, writing up a requirement that was deferred here on 2026-07-25 and then never recorded.yo61/go-udapdecision 2026-07-25 chose Last Light over a per-repo workflow — correctly, the concern is fleet-wide — and cited a path in this repo for the requirement. That file was never created and this repo contains no reference tominimumReleaseAgeanywhere.go-udap#187hit the same failure again on 2026-08-04 as a result.The problem
pnpm 11 enforces
minimumReleaseAge(default 1440 min) onpnpm install --frozen-lockfile, rejecting lockfile entries published inside that window. Dependabot regenerates the lockfile on every bump, so it routinely pulls in a package published minutes earlier:Nothing is broken. The same lockfile passes untouched a few hours later.
Dependabot's own
cooldowncannot prevent this — it gates the direct bump targets, while these failures come from transitively re-resolved deps. Ongo-udap#176the direct deps were 8–10 days old and correctly gated; the breakage was a coordinated@radix-ui/*release published ~3h earlier.Why it needs new machinery
Every retry path today is event-driven — attempt N+1 fires when
pr.checks_failedarrives after our push. Here nothing pushes, and no event fires when a clock passes.No existing
DiagnosisClassfits either.upstream-brokenis closest (not our fault, self-heals, attempt-free) but is detected frombaseChecksStateand waits for a base-goes-green event that never comes. The doc has the full table.The useful part
The clear time is computable from the error text — no npm query, no polling:
So one scheduled rerun at
retryAt, viarerun-failed-jobs. No commit, no sandbox.What the doc rules out
go-udap#185→#187is the worked example:@dependabot recreatediscarded a fully green run and produced one failing on two different fresh packages.required_signatures. See docs: plan signed branch updates for signature-enforcing repos #19.minimumReleaseAge: 0— discards supply-chain protection to dodge a self-resolving delay.Four open questions are left explicit, the main one being where a one-shot timer at an arbitrary computed timestamp lives, given the crons are fixed-schedule sweeps.
🤖 Generated with Claude Code