Skip to content

feat(routing): say when an account is refusing, and route around it #997

feat(routing): say when an account is refusing, and route around it

feat(routing): say when an account is refusing, and route around it #997

Workflow file for this run

name: CI
on:
pull_request:
branches:
- main
push:
branches:
- main
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
- uses: oven-sh/setup-bun@v2
- run: bun install
- run: npm run typecheck
- run: npm test
- run: bun run build
# Windows smoke test — catches platform-specific regressions in:
# - claude-code postinstall on Windows (issue #445 class)
# - the resolver picking up the bundled binary or platform-package binary (#417)
# - cross-platform path handling in TS code
# - bundle output spawning under node on Windows
# We don't run the full unit suite here — that's covered by the Linux job
# above and the resolver's logic is mock-driven so platform doesn't matter
# for unit-level coverage. This job exercises the REAL install + start flow.
windows-smoke:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
- uses: oven-sh/setup-bun@v2
# Install — this also runs claude-code's postinstall, which is the
# single most common source of Windows install-time bugs.
#
# NOTE: bun runs lifecycle scripts through its own bundled POSIX-ish
# shell, even on Windows. That makes this step blind to POSIX syntax in
# our own `postinstall` — see the npm step below.
- run: bun install
# Run our `postinstall` the way npm does: through cmd.exe. bun's shell
# happily parses POSIX constructs like `2>/dev/null || true` on Windows,
# so the `bun install` above cannot catch them — which is exactly how
# #688 shipped a postinstall that broke `npm install` for every Windows
# user while this job stayed green.
#
# `npm run` uses script-shell (cmd.exe on Windows) for the script body,
# so a non-portable postinstall fails here with a non-zero exit.
- name: postinstall must be cmd.exe-portable
run: npm run postinstall
shell: cmd
# Run only the resolver tests on Windows. Useful even though they use
# mocked deps, because the test runner itself exercises path joining,
# file URL parsing, etc., on the real Windows runtime.
- run: bun test src/__tests__/claude-executable-resolver.test.ts
shell: bash
# Plugin loader exercises `await import(absolutePath)` which on Windows
# rejects raw backslash paths with `Received protocol c:`. The test
# writes a real plugin file to a tempdir and loads it, so it actually
# round-trips the buggy code path on the Windows runtime — keeps regressions
# in `loader.ts`'s URL handling from sneaking back in. See PR #480.
- run: bun test src/__tests__/plugin-loader.test.ts
shell: bash
# Build the bundle.
- run: bun run build
shell: bash
# Smoke-start: launch the bundled CLI in the background, hit /health,
# verify it responds with a body. We don't have Claude auth in CI, so
# the auth check inside /health may report loggedIn:false — that's
# fine; what we're verifying is the resolver finds the executable
# and the proxy boots without throwing.
- name: smoke start + /health probe
shell: pwsh
run: |
$env:MERIDIAN_PORT = "3458"
$env:MERIDIAN_HOST = "127.0.0.1"
# Start meridian in the background.
$proc = Start-Process -FilePath "node" -ArgumentList "dist/cli.js" -PassThru -RedirectStandardOutput "meridian.out.log" -RedirectStandardError "meridian.err.log" -WindowStyle Hidden
try {
# Poll /health up to 30s.
$ok = $false
for ($i = 0; $i -lt 30; $i++) {
Start-Sleep -Seconds 1
try {
$r = Invoke-WebRequest -Uri "http://127.0.0.1:3458/health" -UseBasicParsing -TimeoutSec 2
if ($r.StatusCode -eq 200) { $ok = $true; break }
} catch { }
}
if (-not $ok) {
Write-Host "===== meridian.out.log ====="
if (Test-Path "meridian.out.log") { Get-Content "meridian.out.log" }
Write-Host "===== meridian.err.log ====="
if (Test-Path "meridian.err.log") { Get-Content "meridian.err.log" }
throw "Meridian did not respond on /health within 30s — resolver or startup likely failed on Windows"
}
$body = Invoke-RestMethod -Uri "http://127.0.0.1:3458/health" -TimeoutSec 5
Write-Host "/health response: $($body | ConvertTo-Json -Compress)"
if (-not $body.version) { throw "/health missing version field" }
Write-Host "OK — meridian booted on Windows and /health returned a version."
}
finally {
try { Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue } catch { }
}
# Merge settings are load-bearing, not cosmetic. Release Please parses every
# commit on main for the changelog, so a squash body carrying branch commit
# subjects produces DUPLICATE entries — that is what #693/#700 hit via
# --merge, and the same trap reaches --squash for any 2+ commit PR.
#
# PR_TITLE + BLANK closes it structurally: the changelog line is the PR
# title, and the body holds nothing but the Co-authored-by trailer GitHub
# appends (verified — contributor credit survives BLANK).
#
# This exists so the invariant is enforced rather than remembered. If it
# fails, someone changed the repo settings; restore them, do not "fix" this.
# Release Please parses every commit on main, INCLUDING the body. A commit
# whose body carries conventional-commit lines therefore emits the same
# changelog entry twice — that is what #693/#700 hit via --merge, and the
# same trap reaches --squash whenever a branch has 2+ conventional commits.
#
# Repo settings (PR_TITLE + BLANK) prevent it, but settings live outside the
# repo and nothing stops them drifting back. This checks the OUTCOME on main
# rather than the configuration, so it also catches a hand-written --body or
# a stray --merge — any cause, not just the one we thought of.
#
# Deliberately not a settings check: GITHUB_TOKEN cannot read repository
# merge settings at all (there is no such permission scope), so that version
# compared empty strings and failed whether the settings were right or wrong.
changelog-duplication:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Head commit body must not contain conventional-commit lines
run: |
subject=$(git log -1 --format=%s)
body=$(git log -1 --format=%b)
echo "subject: ${subject}"
# Release Please's own release commits are merged with --merge by
# design (squashing breaks its version anchor), so their body legally
# carries the release subject. Exempt only those.
case "${subject}" in
*"release-please"*|*"chore(main): release"*)
echo "release commit — exempt"; exit 0 ;;
esac
if printf '%s\n' "${body}" | grep -q 'release-please--branches'; then
echo "release-please merge commit — exempt"; exit 0
fi
# The optional bullet matters: GitHub's COMMIT_MESSAGES body renders
# each squashed commit as "* feat: subject", while a --merge body
# carries the bare "feat: subject". Match both — missing the bulleted
# form would leave the exact trap this job exists for undetected.
offenders=$(printf '%s\n' "${body}" \
| grep -nE '^[[:space:]]*([*-][[:space:]]+)?(feat|fix|perf|refactor|docs|chore|test|build|ci|style|revert)(\([^)]*\))?!?:' || true)
if [ -n "${offenders}" ]; then
echo "::error::This commit's BODY contains conventional-commit lines:"
printf '%s\n' "${offenders}" | while IFS= read -r l; do echo "::error:: ${l}"; done
echo "::error::Release Please will emit each of these as a duplicate changelog entry."
echo "::error::Cause is usually one of:"
echo "::error:: - repo squash settings drifted off PR_TITLE + BLANK"
echo "::error:: - the PR was merged with a hand-written --body"
echo "::error:: - a non-release PR was merged with --merge instead of --squash"
echo "::error::Check the release PR before it ships; see CLAUDE.md 'Development workflow'."
exit 1
fi
echo "clean — body carries no conventional-commit lines"