Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
2be2ff2
Remove TelemetryDeck and the telemetry config key
claude Sep 15, 2026
ca35b9b
Use swift-crypto for SHA-256 on Linux, CryptoKit on Darwin
claude Sep 15, 2026
785b35d
Replace OSAllocatedUnfairLock with a portable Locked value
claude Sep 15, 2026
c870547
Build the terminal and process layer on Glibc as well as Darwin
claude Sep 15, 2026
769db0d
Fix the Linux-only test failures and start the compatibility matrix
claude Sep 15, 2026
e0e380f
Stop the interactive bridge spinning on a broken stdin
claude Sep 15, 2026
0298bd6
Run build and tests on Ubuntu in CI
claude Sep 15, 2026
d218041
Resolve Homebrew prefixes and installation guidance per platform
claude Sep 15, 2026
4382102
Publish a Linux release artifact alongside the macOS universal binary
claude Sep 15, 2026
c752e4f
Document Linux support, decisions, and platform differences
claude Sep 15, 2026
72e5968
Flatten the nested #if in the default Homebrew prefix
claude Sep 15, 2026
62b005d
Resolve the home directory from $HOME on both platforms
claude Sep 15, 2026
d1c2d8d
Close the process lock descriptor on exec
claude Sep 15, 2026
434737a
Stop the raw-mode pickers spinning when stdin ends
claude Sep 15, 2026
21285c1
Harden both ends of the PTY bridge and check every child allocation
claude Sep 15, 2026
a1539be
Keep the brew hint where brew can act, and own the platform branch
claude Sep 15, 2026
6308109
Assert what the tests claim to assert
claude Sep 15, 2026
1117714
Run the Linux suite serially and say why
claude Sep 15, 2026
f803b1f
Fix the tap commit's missing identity and bring the workflows up to date
claude Sep 15, 2026
e410746
Finish the documentation sweep: doc wording fixes and stale comments
claude Sep 15, 2026
5dd6b7e
Install libcurl4-openssl-dev before swiftly on Linux runners
claude Sep 15, 2026
819b40b
Address the Copilot review: stdin hangup, doctor hint, install path, …
claude Sep 15, 2026
8b82a73
Resolve the home directory and Homebrew prefix from one place on both…
bguidolim Sep 15, 2026
1691b3c
Treat a hangup under a raw-mode yes/no prompt as No, and say so
bguidolim Sep 15, 2026
680b5d9
Table-test the PTY bridge's poll decisions
bguidolim Sep 15, 2026
10dd1a9
Print why an interactive command failed when the bridge itself did
bguidolim Sep 15, 2026
0a9adf2
Make every branch of ensureClaudeCLI testable without a host brew or …
bguidolim Sep 15, 2026
ad88107
Assert the doctor hints as they are, not as the Linux wording assumed
bguidolim Sep 15, 2026
cf6405a
Correct the comments and docs the port left imprecise
bguidolim Sep 15, 2026
0bfda1f
Install the Linux toolchain through one composite action
bguidolim Sep 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .github/actions/setup-swift-linux/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Set up Swift on Linux
description: Install a pinned Swift toolchain on an Ubuntu runner via swiftly and put it on PATH.

inputs:
swift-version:
description: The toolchain version swiftly installs and selects (for example 6.2.0).
required: true

runs:
using: composite
steps:
- name: Install the toolchain's system dependencies
# swiftly refuses to install a toolchain until Swift's documented Ubuntu packages are
# present; libcurl4-openssl-dev is the one ubuntu-latest lacks.
shell: bash
run: sudo apt-get update && sudo apt-get install -y libcurl4-openssl-dev

- name: Install Swift via swiftly
# Unpacked outside the checkout so the tree the caller builds and tests in stays clean.
shell: bash
env:
SWIFT_VERSION: ${{ inputs.swift-version }}
run: |
cd "$RUNNER_TEMP"
curl -O "https://download.swift.org/swiftly/linux/swiftly-$(uname -m).tar.gz"
tar zxf "swiftly-$(uname -m).tar.gz"
./swiftly init --quiet-shell-followup --assume-yes --skip-install
# env.sh lives under $XDG_DATA_HOME when that is set; fail loudly rather than silently
# continuing without swiftly on PATH if the layout ever moves.
ENV_SH="${XDG_DATA_HOME:-$HOME/.local/share}/swiftly/env.sh"
test -f "$ENV_SH" || { echo "::error::swiftly env.sh not found at $ENV_SH"; exit 1; }
. "$ENV_SH"
swiftly install --use "$SWIFT_VERSION" --assume-yes
echo "$SWIFTLY_BIN_DIR" >> "$GITHUB_PATH"
2 changes: 1 addition & 1 deletion .github/workflows/auto-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
private-key: ${{ secrets.APP_PRIVATE_KEY }}

- name: Checkout with full history
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
token: ${{ steps.app-token.outputs.token }}
fetch-depth: 0
Expand Down
56 changes: 51 additions & 5 deletions .github/workflows/pr-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,16 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

env:
# Keep in sync with release.yml, which pins the same toolchain for the release build.
SWIFT_VERSION: '6.2.0'

jobs:
lint:
name: Lint
runs-on: macos-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7

- name: Install linters
run: |
Expand All @@ -37,9 +41,9 @@ jobs:
matrix:
os: [macos-15, macos-26]
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7

- uses: actions/cache@v5
- uses: actions/cache@v6
with:
path: |
.build/checkouts
Expand All @@ -57,14 +61,56 @@ jobs:
- name: Test
run: swift test

build-and-test-linux:
name: Build & Test (linux)
needs: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7

# Not a `container:` job: the official swift images run as root, and
# UpdateCheckerTests.registryWriteFailureContract asserts that a write into a 0555
# directory fails, which root ignores.
- uses: ./.github/actions/setup-swift-linux
with:
swift-version: ${{ env.SWIFT_VERSION }}

- uses: actions/cache@v6
with:
path: |
.build/checkouts
.build/repositories
.build/workspace-state.json
key: spm-linux-${{ hashFiles('Package.swift') }}
restore-keys: spm-linux-

- name: Swift version
run: swift --version

- name: Resolve packages
run: swift package resolve

# --no-parallel: corelibs Foundation opens files for writing without O_CLOEXEC, on both its
# atomic and non-atomic paths, so a test that forks while another is mid-write leaks that
# write descriptor into the child and a later exec of the same file fails with ETXTBSY.
# Measured at roughly one failure in four full runs. mcs itself is unaffected — it never
# executes a file it has just written — so this is a test-runner constraint, not a product
# limitation, and the macOS jobs stay parallel.
- name: Test
run: swift test --no-parallel

# The shape release.yml ships. Without this, a break in it first shows up on a tag.
- name: Build release binary
run: swift build -c release --static-swift-stdlib

build-universal:
name: Universal Binary
needs: lint
runs-on: macos-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7

- uses: actions/cache@v5
- uses: actions/cache@v6
with:
path: |
.build/checkouts
Expand Down
136 changes: 119 additions & 17 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,16 @@ permissions:
concurrency:
group: release

env:
# Keep in sync with pr-checks.yml.
SWIFT_VERSION: '6.2.0'

jobs:
verify-version:
name: Verify Version
runs-on: macos-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7

- name: Verify tag matches source version
env:
Expand All @@ -34,9 +38,9 @@ jobs:
needs: verify-version
runs-on: macos-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7

- uses: actions/cache@v5
- uses: actions/cache@v6
with:
path: |
.build/checkouts
Expand All @@ -53,9 +57,9 @@ jobs:
needs: resolve
runs-on: macos-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7

- uses: actions/cache/restore@v5
- uses: actions/cache/restore@v6
with:
path: |
.build/checkouts
Expand All @@ -67,14 +71,14 @@ jobs:
- name: Test
run: swift test

build-and-release:
name: Build and Release
build-macos:
name: Build macOS Universal Binary
needs: [test]
runs-on: macos-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7

- uses: actions/cache/restore@v5
- uses: actions/cache/restore@v6
with:
path: |
.build/checkouts
Expand All @@ -99,25 +103,118 @@ jobs:
fi
echo "Binary verified: ${BUILT_VERSION}"

- name: Prepare artifacts
- name: Prepare artifact
env:
TAG_NAME: ${{ github.ref_name }}
run: tar -czf "mcs-${TAG_NAME}-macos-universal.tar.gz" -C "${BIN_DIR}" mcs

- uses: actions/upload-artifact@v7
with:
name: mcs-macos-universal
path: mcs-${{ github.ref_name }}-macos-universal.tar.gz
if-no-files-found: error

test-linux:
name: Test (linux)
needs: verify-version
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7

- uses: ./.github/actions/setup-swift-linux
with:
swift-version: ${{ env.SWIFT_VERSION }}

- uses: actions/cache@v6
with:
path: |
.build/checkouts
.build/repositories
.build/workspace-state.json
key: spm-linux-${{ hashFiles('Package.swift') }}
restore-keys: spm-linux-

# --no-parallel for the same reason as pr-checks.yml: corelibs Foundation leaks write
# descriptors into concurrently forked children, which surfaces as a spurious ETXTBSY.
- name: Test
run: swift test --no-parallel

build-linux:
name: Build Linux x86_64 Binary
needs: [test, test-linux]
# ubuntu-22.04 on purpose: it sets the glibc floor of the published binary at 2.35.
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v7

- uses: ./.github/actions/setup-swift-linux
with:
swift-version: ${{ env.SWIFT_VERSION }}

- uses: actions/cache@v6
with:
path: |
.build/checkouts
.build/repositories
.build/workspace-state.json
key: spm-linux-${{ hashFiles('Package.swift') }}
restore-keys: spm-linux-

- name: Build release binary
run: |
TARBALL="mcs-${TAG_NAME}-macos-universal.tar.gz"
tar -czf "${TARBALL}" -C "${BIN_DIR}" mcs
shasum -a 256 "${TARBALL}" > SHA256SUMS
echo "TARBALL=${TARBALL}" >> "$GITHUB_ENV"
swift build -c release --static-swift-stdlib
echo "BIN_DIR=$(swift build -c release --static-swift-stdlib --show-bin-path)" >> "$GITHUB_ENV"

- name: Verify binary
env:
TAG_NAME: ${{ github.ref_name }}
run: |
BUILT_VERSION=$("${BIN_DIR}/mcs" --version)
if [ "$BUILT_VERSION" != "$TAG_NAME" ]; then
echo "::error::Built binary reports version '${BUILT_VERSION}' but tag is '${TAG_NAME}'"
exit 1
fi
echo "Binary verified: ${BUILT_VERSION}"

- name: Prepare artifact
env:
TAG_NAME: ${{ github.ref_name }}
run: tar -czf "mcs-${TAG_NAME}-linux-x86_64.tar.gz" -C "${BIN_DIR}" mcs

- uses: actions/upload-artifact@v7
with:
name: mcs-linux-x86_64
path: mcs-${{ github.ref_name }}-linux-x86_64.tar.gz
if-no-files-found: error

release:
name: Release
needs: [build-macos, build-linux]
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v8
with:
merge-multiple: true

- name: Checksum artifacts
env:
TAG_NAME: ${{ github.ref_name }}
run: |
# sha256sum, not shasum: identical "<hash> <file>" output and present on every runner.
sha256sum *.tar.gz > SHA256SUMS
cat SHA256SUMS
echo "TARBALL=mcs-${TAG_NAME}-macos-universal.tar.gz" >> "$GITHUB_ENV"

- name: Create GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG_NAME: ${{ github.ref_name }}
TARBALL: ${{ env.TARBALL }}
run: |
gh release create "${TAG_NAME}" \
--repo "${{ github.repository }}" \
--verify-tag \
--generate-notes \
"${TARBALL}" SHA256SUMS
*.tar.gz SHA256SUMS

- name: Update Homebrew tap
env:
Expand All @@ -126,10 +223,15 @@ jobs:
TARBALL: ${{ env.TARBALL }}
if: env.HOMEBREW_TAP_TOKEN != ''
run: |
SHA=$(shasum -a 256 "${TARBALL}" | cut -d' ' -f1)
# The formula keeps pointing at the macOS tarball; Linux ships as a plain release asset.
SHA=$(sha256sum "${TARBALL}" | cut -d' ' -f1)

git clone "https://x-access-token:${HOMEBREW_TAP_TOKEN}@github.com/mcs-cli/homebrew-tap.git" tap
cd tap
# This job has no checkout and Ubuntu runners have dotless hostnames, so git cannot
# auto-detect an author and would refuse the commit. Same identity as auto-release.yml.
git config user.email "autorelease@mcs-cli.dev"
git config user.name "MCS Auto Release"
cat > Formula/mcs.rb << FORMULA
class Mcs < Formula
desc "Configure Claude Code with MCP servers, plugins, skills, and hooks"
Expand Down
Loading
Loading