Repository navigation
feat(release): automate lash releases without an interactive OTP (LAC-4453) - #50
lacymorrow wants to merge 4 commits into
Conversation
…-4453) Every lash release was a manual run on a laptop, and the npm step could not be done by an agent at all: publish-lash.ts read a 2FA code from stdin, and no workflow in .github/workflows matched lacymorrow/lash, so none ever ran. - Add .github/workflows/release-lash.yml. workflow_dispatch, gated to lacymorrow/lash, always builds the lash branch. One ubuntu runner, because build-lash.ts already cross-compiles all eleven targets, so a build matrix would add plumbing for nothing. A dry_run input checks the credential and the next version without publishing. - Add npm-auth.ts. With NPM_TOKEN set, point npm at a 0600 userconfig holding the token and drop the OTP flag entirely; granular automation tokens bypass 2FA. Without a token, the interactive prompt stays as the local fallback. verifyNpmAuth runs before the build so a dead token costs two seconds rather than a full build followed by a 401. - Add release-version.ts plus tests. Patch-bump whichever of npm and the newest GitHub release is ahead, then skip versions whose tag already exists. v1.7.14 was tagged by a run that died before publishing, so reusing it would have shipped an older commit than HEAD. - release-lash.ts: refuse to run off the lash branch rather than inferring intent from the preview channel, fail instead of warning when no archives were built, reuse a release a previous run left half finished, append to GITHUB_OUTPUT instead of truncating it, and publish to npm before the homebrew tap so a missing cross-repo token cannot block the npm publish. - Drop the awk version bump from the root release script so the version has one definition. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…r in tmp Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two bugs that only show up on a runner, both found by re-reading rather than by a failing test, because nothing exercises this path until a real release. - updateRegistries resolves the archives as ./dist/lashcode-*.tar.gz, but release-lash.ts chdir's back to the repo root before calling it, so every sha256sum looked in <root>/dist instead of packages/opencode/dist and found nothing. This predates the current change: the homebrew step could not have succeeded when driven from release-lash.ts. Run it from packages/opencode and restore the cwd either way. - The workflow set the git identity at repo level, but the tap is a second clone, so its "Update to v<version>" commit would fail on an unknown author. Use --global. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Pushed two more fixes after re-reading the CI path. Both are bugs that only appear on a runner, so no test caught them. 1. The homebrew step could never have worked when driven from 2. The tap commit would have failed on an unknown author. The workflow set CI note
One correctionIn the board comment I claimed npm already has 1.7.9 through 1.7.11. It does not: The original issue was right. What is true is narrower: the newest npm version matches the newest GitHub release, so "npm matches latest tag" reads green while three intermediate versions are missing. Nothing in this PR changes; backfilling old patches of a fork is not worth doing. |
getLatestRelease hits api.github.com unauthenticated, which Actions runners rate limit because they share outbound IPs. It was called twice: once guarded inside resolveVersion, once unguarded for the changelog range, so a 403 on the second call killed the release after the credential check had passed. Fetch it once, reuse it for both, and treat a miss as unknown: the version falls back to npm alone and the changelog is skipped with a warning rather than throwing. The branch guard still runs before any network call. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Code review: changes requestedPosted as a comment because Reviewed at Blocking1.
|
Follow-up: answers to the three checks, plus one more finding1. Is the cwd restored on every exit path?Yes. 2. Can the degraded path produce a bad version?No, and the reason is worth writing down because it is the whole safety argument. With That works because Which is the reason finding 3 in my first pass matters more than it looks: 3. Does
|
CI triage at
|
bun run releaseon thelashbranch now completes the npm publish with nobody typing a code, and the same thing can be triggered from the Actions tab.The problem
Two independent blockers, both verified on
origin/lash:.github/workflows/came from upstream and every publish-capable one is gatedif: github.repository == 'anomalyco/opencode'or'sst/opencode'. Nothing matchedlacymorrow/lash, so nothing ever ran.publish-lash.tsprompted via readline and passed--otp=on everynpm publish. A human had to type a code that expires in ~30 seconds, so an agent could not do it and no token fixed it alone.Net effect: npm
lashcodehas been stuck at 1.7.13 since 2026-05-05.v1.7.14is tagged and pushed with no GitHub release and no npm publish.What changed
.github/workflows/release-lash.yml(new).workflow_dispatch, gatedif: github.repository == 'lacymorrow/lash', pinned toref: lashso a release can only ever come off the release branch. Optionalversioninput for a minor or a major; optionaldry_runinput that checks the credential and the next version, then stops.One
ubuntu-24.04runner, no build matrix:build-lash.tsalready cross-compiles all eleven targets, so a matrix would add eleven jobs and artifact plumbing for no benefit. Also left out: AUR, ghcr, vscode, desktop, and code signing. None of them are part of a lash CLI release today, and signing is a separate decision.packages/opencode/script/npm-auth.ts(new). WithNPM_TOKENpresent it writes a0600npmrc to a temp path, points npm at it vianpm_config_userconfig, and the--otpflag is dropped entirely. Granular automation tokens bypass 2FA, which is the whole mechanism.verifyNpmAuth()runs before the build, so a dead token fails in about two seconds with the command needed to replace it instead of after a fifteen minute build.packages/opencode/script/release-version.ts(new, with tests). Patch-bumps whichever of npm and the newest GitHub release is further ahead, then skips any version whose tag already exists. This matters immediately: both sources read 1.7.13, so a naive patch bump lands on 1.7.14, whose tag already points at an older commit thanHEAD. The next release is 1.7.15.packages/opencode/script/publish-lash.ts.publishWithInteractiveOtpsplit intopublishPackages(picks the path) andpublishBatch(does the work,--otpconditional). The interactive prompt is untouched as the local fallback. The existing idempotency check is kept, so a rerun skips what is already on the registry.script/release-lash.ts. Refuses to run offlashrather than inferring intent from the preview channel. Throws instead of warning when zero archives were built. Reuses a release a previous run left half finished (gh release upload --clobber). Appends toGITHUB_OUTPUTinstead of truncating it, and no longer writes it twice, which was silently dropping thereleaseandtagoutputs. Publishes to npm before the homebrew tap, so a missing cross-repo token cannot block the npm publish.package.json. Dropped the inlineawkversion bump from thereleasescript so the version has one definition.LASH_RELEASE.md. Documents the workflow, the dry run, and the three credentials. The old text described the OTP prompt as the only path and pointed at abun run publishscript that does not exist.Verification
bun test test/release-version.test.tsv4.7,v2.0.0must not steer the version)bun run typecheckinpackages/opencodetsgo --noEmitonscript/release-lash.tspackages/sdk/js/src/gen/client/client.gen.tsbunx oxlinton the three changed scriptsbun run script/release-lash.tsofflash, exits non-zero:Releases are cut from the lash branch. You are on LAC-4453/automate-lash-release.verifyNpmAuth()logsno NPM_TOKEN set, publish will prompt for an OTP, does not throw, leavesnpm_config_userconfigunset100600version,dry_run; jobifisgithub.repository == 'lacymorrow/lash'bun install @opentui/core@catalog:build-lash.tsinstall line worksStill unverified by nature: the real end-to-end run. That needs the credential, see below.
What Lacy needs to do
NPM_TOKEN. A secret by that name exists on the repo from 2025-09-06, but the token in~/.npmrcreturns 401, so it is likely the same dead classic token. It must be a granular automation token forlashcodewith read and write permission, not a classic one. Run release lash with dry_run ticked: it either confirms the token in two minutes or prints exactly what to fix.HOMEBREW_TAP_TOKEN. The tap push goes tolacymorrow/homebrew-tap, which is cross-repo, so Actions'GITHUB_TOKENcannot reach it. Without this the release and the npm publish still succeed and the run ends red on the brew step alone.Then a real run should produce 1.7.15 on npm, a GitHub release with all eleven archives, and an updated
lash.rb.Issue: LAC-4453. Parent: LAC-2396, open since July for this reason.
🤖 Generated with Claude Code