developing-on-linux
Sets up Swift linting and formatting on Linux to match macOS CI environments.
Install
mkdir -p .claude/skills/developing-on-linux && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/14653" && unzip -o skill.zip -d .claude/skills/developing-on-linux && rm skill.zipInstalls to .claude/skills/developing-on-linux
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Use when working on this repo from a Linux box (e.g. Claude Code on the web, a Linux container, CI debugging) where Xcode and the macOS `just` toolchain are unavailable. Covers installing a Swift toolchain plus version-matched `swift-format` and `swiftlint`, then running format / lint / typecheck locally so you don't burn a 25-minute macOS CI round-trip to discover a formatting or compile error. Use whenever `swift`, `swift-format`, or `swiftlint` is "command not found", or a task says to fix `format-check` / lint / build issues but `just` can't run.Key capabilities
- →Install a Swift toolchain on Linux
- →Install version-matched `swift-format` on Linux
- →Install version-matched `swiftlint` on Linux
- →Run `swift-format` for byte-parity checks
- →Run `swiftlint` for strict linting
- →Type-check Foundation-only Swift files without Xcode
How it works
The skill provides steps to install a Swift toolchain, `swift-format`, and `swiftlint` on Linux, ensuring version matching with CI. It then outlines commands to run format checks, strict linting, and type-checking for Foundation-only files.
Inputs & outputs
When to use developing-on-linux
- →Setting up Linux development
- →Matching CI tool versions
- →Fixing linting on Linux
About this skill
Developing on Linux
The project builds and tests on macOS only (just build-mac, just test, and CI all run xcodebuild on macos-26). On a Linux host none of that runs. But the two gates that catch the most mistakes — just format-check (swift-format byte-parity + swiftlint --strict) and basic Swift type-checking — can be reproduced on Linux if you install the matching tools. Doing so turns a ~25-minute "push → wait for CI → read failure" loop into a local seconds-long loop.
The one rule: match CI's pinned versions
CI installs exact tool versions (scripts/install-ci-tools.sh, PINNED_TOOLS). format-check does a byte-for-byte cmp against swift-format output, so a different swift-format version will reformat differently and fail CI even when your code is fine. Always read the current pins first:
grep -A4 'PINNED_TOOLS=' scripts/install-ci-tools.sh
# e.g. swiftlint|0.63.3|... swift-format|602.0.0|... xcodegen|2.45.4|...
swift-format versioning maps to the Swift release: 602.0.0 ⇔ Swift 6.2. The toolchain-bundled swift-format is built from the same source tag as the standalone Homebrew bottle, so the Swift 6.2 Linux toolchain's swift-format produces output identical to CI's 602.0.0. (It self-reports as 6.2.0 — same thing.) If the pin moves to 6xx.y.z, install the matching Swift 6.x toolchain.
Setup (Ubuntu x86_64)
Took ~2–3 min on a fresh container; the toolchain tarball is ~1 GB.
# 1. Swift toolchain (provides swiftc, swift-format, and libsourcekitdInProc.so).
# Pick the URL matching your distro from https://www.swift.org/install/linux/
cd /tmp
curl -sSL -o swift.tar.gz \
https://download.swift.org/swift-6.2-release/ubuntu2404/swift-6.2-RELEASE/swift-6.2-RELEASE-ubuntu24.04.tar.gz
mkdir -p swift-toolchain && tar xzf swift.tar.gz -C swift-toolchain
TC="/tmp/swift-toolchain/$(ls /tmp/swift-toolchain)/usr"
# 2. SwiftLint Linux binary at the PINNED version (realm/SwiftLint ships
# swiftlint_linux_amd64.zip in its GitHub releases).
curl -sSL -o swiftlint.zip \
https://github.com/realm/SwiftLint/releases/download/0.63.3/swiftlint_linux_amd64.zip
mkdir -p swiftlint-bin && (cd swiftlint-bin && unzip -o ../swiftlint.zip)
chmod +x swiftlint-bin/swiftlint
# 3. Persist an env file you can `source` in later Bash calls (shell state
# does NOT persist between Bash tool calls — re-source it each time, or
# inline the exports).
cat > /tmp/swiftenv.sh <<EOF
export PATH="$TC/bin:/tmp/swiftlint-bin:\$PATH"
export LD_LIBRARY_PATH="$TC/lib:\${LD_LIBRARY_PATH:-}"
EOF
source /tmp/swiftenv.sh
swift-format --version # expect 6.2.0 (== CI's 602.0.0)
swiftlint version # expect 0.63.3
swiftlint needs the toolchain's libsourcekitdInProc.so. Without it you get Fatal error: Loading libsourcekitdInProc.so failed. Setting LD_LIBRARY_PATH to the toolchain's usr/lib (as above) fixes it — this is why you install the full toolchain even though you "only" want the linter.
Running the gates locally
Reproduce just format-check exactly (format byte-parity, then strict lint):
source /tmp/swiftenv.sh
files=$(git ls-files '*.swift' ':!:Vendored/**') # or scope to your changed files
# (a) apply formatting in place — equivalent to `just format`'s swift-format step
swift-format format -i --configuration .swift-format $files
# (b) byte-parity check — what CI's format-check actually asserts
for f in $files; do
cmp -s "$f" <(swift-format format --configuration .swift-format "$f") \
|| echo "NOT FORMATTED: $f"
done
# (c) strict lint — respects nested configs (e.g. MoolahTests/.swiftlint.yml)
swiftlint lint --strict --no-cache $changed_dirs
Scope swiftlint to the directories you touched for speed; it reads .swiftlint.yml from the repo root and honours nested overrides automatically.
Lint rules that bite, and how to satisfy them
multiline_parameters/multiline_arguments—.swift-formathasrespectsExistingLineBreaks: true, so it preserves a layout you write. Write wrapped parameter/argument lists one per line (matching existing files likeShared/AccountPerformanceCalculator.swift) and swift-format keeps them, satisfying the rule.multiline_argumentsis also blanket-disabled inMoolahTests/and may be file-level-disabled on production files per.swiftlint.yml— prefer one-per-line over adding a disable.function_parameter_count(>5) — bundle related args into a smallprivate struct.unneeded_synthesized_initializer— delete a memberwise/init() {}that exactly matches what the compiler would synthesize (note: an init that adds default values the properties lack is NOT redundant — keep it).identifier_name— min 3 chars (onlyid, x, y, i, j, n, ok, toare exempt). Nocv,mu,sd.- For the full policy and the "never reintroduce a baseline / don't
// swiftlint:disableto dodge debt" rules, load thefixing-format-checkskill.
Type-checking without Xcode
You can't build the app target on Linux — some files use Darwin-only APIs (import os/OSAllocatedUnfairLock, import OSLog, import CryptoKit, RelativeDateTimeFormatter, etc.) and there's no iOS/macOS SDK. But you can still catch the bulk of compile errors in a layer you're editing by assembling a throwaway type-check harness of just the Foundation-only files it depends on:
source /tmp/swiftenv.sh
rm -rf /tmp/tc && mkdir -p /tmp/tc
cp Domain/Models/*.swift Domain/Models/CSVImport/*.swift Domain/Services/*.swift /tmp/tc/
cp Shared/FinancialMonth.swift Shared/Array+Uniqued.swift /tmp/tc/
cp <your-layer>/*.swift /tmp/tc/
# Shim/skip the few Darwin-only stragglers, then iterate:
# - replace `import os` and shim OSAllocatedUnfairLock with an NSLock wrapper
# - rm files that import OSLog/CryptoKit or use RelativeDateTimeFormatter
# IF your layer doesn't depend on them (the compiler tells you what's missing)
swiftc -typecheck /tmp/tc/*.swift 2>&1 | grep error: | sort -u
Iterate: each error names either a real bug in your code (fix it in the repo) or a missing dependency / Linux-Foundation gap in an unrelated file (add the dep, or drop the file if your layer doesn't use it). Reaching 0 errors means your layer compiles against the real domain type signatures — it does not guarantee the macOS build, but it eliminates the common mistakes (wrong initializer labels, Decimal-vs-Double arithmetic, missing members).
For test files: strip @testable import Moolah and import Testing and fold the support file into the same harness to type-check the test builders (the @Test/#expect macros themselves need the Testing module and won't compile here).
What still only CI can verify
- The actual
xcodebuildcompile of the whole app + the iOS/macOS-specific code paths. - Test execution (assertions, async behaviour) and UI tests.
validate-appstore,no-swiftdata,validate-todos, schema additivity (these are quick shell/justchecks; you can run several directly ifjustis present, but they don't need the toolchain above).
So: use the local loop to land format/lint/typecheck clean, then push and let CI close the loop on build + tests. When monitoring that CI run, follow the landing-prs skill's guidance and subscribe_pr_activity rather than polling.
When not to use it
- →When working on macOS with Xcode and the `just` toolchain available
- →When the project does not use Swift
- →When not debugging CI failures related to formatting or linting
Limitations
- →The project builds and tests on macOS only
- →The app target cannot be built on Linux due to Darwin-only APIs
- →Test execution and UI tests still require CI verification
How it compares
This skill enables local Swift development and pre-CI validation on Linux, replicating macOS CI gates, unlike relying solely on macOS or remote CI for feedback.
Compared to similar skills
developing-on-linux side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| developing-on-linux (this skill) | 0 | 2mo | Review | Advanced |
| release-skills | 2 | 3mo | Review | Intermediate |
| release-bump | 1 | 5mo | No flags | Beginner |
| domain-iot | 1 | 6mo | No flags | Advanced |
Try saying
Example prompts that trigger this skill in your AI assistant.
You might also like
release-skills
JimLiu
Universal release workflow. Auto-detects version files and changelogs. Supports Node.js, Python, Rust, Claude Plugin, and generic projects. Use when user says "release", "发布", "new version", "bump version", "push", "推送".
release-bump
mikeyobrien
Use when bumping ralph-orchestrator version for a new release, after fixes are committed and ready to publish
domain-iot
actionbook
Use when building IoT apps. Keywords: IoT, Internet of Things, sensor, MQTT, device, edge computing, telemetry, actuator, smart home, gateway, protocol, 物联网, 传感器, 边缘计算, 智能家居
domain-cloud-native
actionbook
Use when building cloud-native apps. Keywords: kubernetes, k8s, docker, container, grpc, tonic, microservice, service mesh, observability, tracing, metrics, health check, cloud, deployment, 云原生, 微服务, 容器
analyze-ci-speed
Sovereign-Labs
Analyze compilation time and test durations from CI logs. Use when the user asks about slow builds, slow tests, or wants to optimize CI time.
cts-triage
gfx-rs
Run CTS test suites and investigate failures