* ci(macos): give the macOS test lane the 30m timeout the Linux lane already has #6091 gave scripts/ci/pr-core.sh an explicit -timeout=30m because ./cmd/bd has outgrown Go's implicit 10m per-package default. The macOS lanes invoke `go test` inline in workflow YAML and never got the same treatment, so they still run against a 10m wall. Measured macOS ./cmd/bd durations across 17 recent runs: 357, 387, 389, 418, 419, 422, 486, 494, 536, 542, 547, 549, 558, 560, 590, 597, 600 — a coin flip against 600s. #6091's own macOS lane passed at 596.719s, 3.3s of margin. No single test is responsible, so when the alarm fires the panic names whichever test was in flight and reads like a hang rather than the package running out of clock. 30m is not a new number: main.yml's integration shards, ci-measurements.yml and nightly.yml already give this same code 30m on every -race lane. Changed: - pr.yml Test (macos-latest) - main.yml Test (macos-latest), and the ubuntu-latest coverage lane beside it, which runs the same ./... under -race with coverage on and also had no explicit timeout - ci-measurements.yml "macos short" and "pr-core" — the measurement twins of the above, which at 10m clip their own longest samples into panics In main.yml the flag sits on the invocation rather than in matrix.test-flags so editing the matrix cannot silently drop it. De-slowing or sharding ./cmd/bd is the real fix and belongs on main; this only stops the clock from being the thing that fails. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(ci): pin the macOS lanes' explicit -timeout=30m TestMacOSTestJobsReuseWorkspaceBDBinary asserts the macOS `Test` step's `run` string verbatim, so adding -timeout=30m to pr.yml and main.yml left the test still asserting the pre-change command. Both the macOS lane and PR Core failed on it: ci_workflow_test.go:282: step "Test" run = "... -timeout=30m ...", want "... -skip '^TestEmbedded' ./..." Update the two pinned strings so the tripwire guards the commands the lanes actually run. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Beads Scripts
Utility scripts for maintaining the beads project.
ci/
Repository-owned CI command wrappers. These scripts are the source of truth for the target CI tiers; Make targets are aliases for local discoverability.
make ci-pr-core
make ci-pr-policy
make ci-pr-lint
make ci-package-mcp
make ci-package-npm
Each wrapper auto-detects the repository root, sources .buildflags when it
invokes Go in the default build mode, and records per-command timing through
scripts/ci/lib/timing.sh.
Broad Go test wrappers also source scripts/ci/lib/test-env.sh, which creates a
temporary HOME/XDG/Dolt root, isolates Git global/system config, clears runtime
Beads/Dolt environment variables, and sets BEADS_TEST_SKIP=dolt before tests
run. This keeps local make test and make ci-pr-core results comparable to
the fast PR-core contract even on shared agent hosts. Set
BEADS_TEST_ENV_RUN_DOLT=1 only when intentionally running the Dolt-dependent
tests through these broad wrappers, or BEADS_TEST_ENV_DISABLE=1 when debugging
against your real local configuration.
The broad Go wrappers also cap package and test parallelism to 4 by default
(GO_TEST_PKG_PARALLEL and GO_TEST_PARALLEL). This avoids turning high-core
shared hosts into a different test topology than GitHub Actions.
make ci-pr-policy includes scripts/check-testing-short.sh, which enforces
that testing.Short() is only used for runtime, stress, or large-fixture skips.
Use build tags, environment checks, or named wrappers for integration/e2e/API
boundaries.
Package gate wrappers validate publishable/package-adjacent surfaces:
make ci-package-mcpbuilds or consumes abdbinary, puts it onPATHasbd, then runs locked MCP packageuv sync, Ruff, mypy, pytest, and build checks.make ci-package-npmbuilds or consumes the native binary expected bynpm-package/bin/bd, runs the npm package test suite, and checksnpm pack --dry-run.
Set BEADS_TEST_BD_BINARY=/path/to/bd for MCP and npm package gates to reuse a
prebuilt candidate binary instead of rebuilding it inside the wrapper.
pr-preflight.sh
Read-only PR safety check for agents and maintainers.
# Before implementing or opening a related PR
./scripts/pr-preflight.sh --search "topic keywords" --repo gastownhall/beads
# Before changing, closing, or merging an existing PR
./scripts/pr-preflight.sh 123 --repo gastownhall/beads
It reports contributor/fork status, draft/review/merge/check state, risky diff
signals such as .beads/ changes or missing tests, and the required
contributor-protection next steps. It does not replace code review or local
validation.
Base-branch health is supplemented by a warn-only PR-gate sample: the last 60
completed pull_request-event runs, grouped per workflow. A single workflow
whose decisive runs (>= 5, across >= 3 distinct head branches) are all
failure-class is reported as a broken PR gate — the case where a job that
exists only in the PR workflow is red for every PR while the base branch shows
green. This detector never blocks (deliberately: automation classifies
unrecognized [block] lines as genuine merge blockers and would park merge
lanes on a false positive); it is skipped entirely while the base branch is
red.
gh-body-lint
Lint Markdown files before posting them with gh ... --body-file.
./scripts/gh-body-lint body.md
./scripts/gh-body-lint --fix body.md
The lint catches literal \n sequences, which render poorly on GitHub, and
GH#123 references, which do not auto-link like #123 or owner/repo#123.
release.sh (⭐ The Easy Button)
One-command release from version bump to local installation.
Usage
# Full release (does everything)
./scripts/release.sh 0.9.3
# Preview what would happen
./scripts/release.sh 0.9.3 --dry-run
What It Does
This master script automates the entire release process:
- ✅ Stops running Dolt servers (avoids version conflicts)
- ✅ Runs tests and linting
- ✅ Bumps version in all files
- ✅ Commits and pushes version bump
- ✅ Creates and pushes git tag
- ✅ Verifies or opens the Homebrew core formula PR
- ✅ Upgrades local Homebrew installation
- ✅ Verifies everything works
After this script completes, your system is running the new version!
Examples
# Release version 0.9.3
./scripts/release.sh 0.9.3
# Preview a release (no changes made)
./scripts/release.sh 1.0.0 --dry-run
Prerequisites
- Clean git working directory
- All changes committed
- golangci-lint installed
- Homebrew installed (for local upgrade)
- Push access to gastownhall/beads
Output
The script provides colorful, step-by-step progress output:
- 🟨 Yellow: Current step
- 🟩 Green: Step completed
- 🟥 Red: Errors
- 🟦 Blue: Section headers
What Happens Next
After the script finishes:
- GitHub Actions builds binaries for all platforms (~5 minutes)
- PyPI package is published automatically
- Homebrew core formula is verified or tracked through its canonical PR
- Users can
brew upgrade beadsto get the new version after Homebrew merges - GitHub Release is created with binaries and changelog
bump-version.sh
Bumps the version number across all beads components in a single command.
Usage
# Show usage
./scripts/bump-version.sh
# Update versions (shows diff, no commit)
./scripts/bump-version.sh 0.9.3
# Update versions and auto-commit
./scripts/bump-version.sh 0.9.3 --commit
What It Does
Updates version in all these files:
cmd/bd/version.go- bd CLI version constantplugins/beads/.claude-plugin/plugin.json- Claude plugin versionplugins/beads/.codex-plugin/plugin.json- Codex plugin version.claude-plugin/marketplace.json- Claude marketplace plugin versionintegrations/beads-mcp/pyproject.toml- MCP server versionREADME.md- Alpha status versionPLUGIN.md- Version requirements
Features
- Validates semantic versioning format (MAJOR.MINOR.PATCH)
- Verifies all versions match after update
- Shows git diff of changes
- Auto-commits with standardized message (optional)
- Cross-platform compatible (macOS and Linux)
Examples
# Bump to 0.9.3 and review changes
./scripts/bump-version.sh 0.9.3
# Review the diff, then manually commit
# Bump to 1.0.0 and auto-commit
./scripts/bump-version.sh 1.0.0 --commit
git push origin main
Why This Script Exists
Previously, version bumps only updated cmd/bd/version.go, leaving other components out of sync. This script ensures all version numbers stay consistent across the project.
Safety
- Checks for uncommitted changes before proceeding
- Refuses to auto-commit if there are existing uncommitted changes
- Validates version format before making any changes
- Verifies all versions match after update
- Shows diff for review before commit
sign-windows.sh
Signs Windows executables with an Authenticode certificate using osslsigncode.
Usage
# Sign a Windows executable
./scripts/sign-windows.sh path/to/bd.exe
# Environment variables required for signing:
export WINDOWS_SIGNING_CERT_PFX_BASE64="<base64-encoded-pfx>"
export WINDOWS_SIGNING_CERT_PASSWORD="<certificate-password>"
What It Does
This script is called automatically by GoReleaser during the release process:
- Decodes the PFX certificate from base64
- Signs the Windows executable using osslsigncode
- Timestamps the signature using DigiCert's RFC3161 server
- Replaces the original binary with the signed version
- Verifies the signature was applied correctly
Prerequisites
osslsigncodeinstalled (apt install osslsigncodeorbrew install osslsigncode)- EV code signing certificate exported as PFX file
- GitHub secrets configured:
WINDOWS_SIGNING_CERT_PFX_BASE64- base64-encoded PFX fileWINDOWS_SIGNING_CERT_PASSWORD- certificate password
Graceful Degradation
If the signing secrets are not configured:
- The script prints a warning and exits successfully
- GoReleaser continues without signing
- The release proceeds with unsigned Windows binaries
This allows releases to work before a certificate is acquired.
Why This Script Exists
Windows code signing helps reduce antivirus false positives that affect Go binaries.
Kaspersky and other AV software commonly flag unsigned Go executables as potentially
malicious due to heuristic detection. See docs/reference/antivirus.md for details.
Future Scripts
Additional maintenance scripts may be added here as needed.