Skip to content

chore(release): v0.1.49 #319

chore(release): v0.1.49

chore(release): v0.1.49 #319

Workflow file for this run

name: CI
on:
push:
branches: [main]
pull_request:
# Least privilege for GITHUB_TOKEN; mirror ci-web.yml.
permissions:
contents: read
# Stacked pushes to the same ref cancel the superseded run instead
# of burning the full 3-OS matrix to completion; mirror ci-web.yml.
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
# Opt every JavaScript-based action (`actions/checkout@v6`,
# `actions/cache@v5`, `actions/setup-node@v5`, `pnpm/action-setup@v5`)
# into the Node 24 runtime ahead of GitHub's June 2026 forced switch.
# Skips the deprecation warning on every job; also surfaces any
# Node-24 incompatibility well before the September 2026 hard
# removal of Node 20 from runners.
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
jobs:
lint:
name: Format / Clippy (Linux)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
components: clippy, rustfmt
# Swatinem/rust-cache keys on job id + toolchain + lockfile and
# prunes stale artifacts, unlike a raw actions/cache of target/
# keyed only on Cargo.lock (which froze at the first
# post-lockfile-change run and re-uploaded multi-GB archives).
- name: Rust build cache
uses: Swatinem/rust-cache@v2
- name: Check formatting
run: cargo fmt --check -p claudepot-core -p claudepot-cli -p xtask
- name: Clippy (core + cli + xtask, all targets)
# `--all-targets` lints test code too. Without it, test-only
# lints accumulate silently between Rust releases and surface
# as a wall of errors at the next local clippy run (see the
# 2026-05-13 cleanup commit for the 7-lint backlog this gate
# would have caught at the source).
run: cargo clippy --all-targets -p claudepot-core -p claudepot-cli -p xtask -- -D warnings
# CC-parity fixtures (parity-harness/README.md): verifies
# claudepot-core's settings-merge semantics against golden
# fixtures generated from CC's own implementation. The xtask
# crate shares the cargo cache above, so the marginal cost is
# one small crate compile plus a millisecond fixture run.
- name: CC-parity fixtures (cargo xtask verify-cc-parity)
run: cargo xtask verify-cc-parity
# ─── Architectural invariants ─────────────────────────────
# Three grep-based tripwires. Each protects an invariant
# called out in dev-docs/codex-plans/20260515-1130-shared-memory.md
# that the type system can't enforce on its own.
- name: Invariant — codex_session parser is a single leaf
# The Codex rollout parser is a neutral leaf in the
# workspace. The decoder pattern is `"session_meta" =>`
# in a match arm — flag that anywhere outside the
# codex_session/ module. We deliberately ignore plain
# string occurrences (test fixtures, comments) because
# those aren't parsers.
run: |
set -euo pipefail
violators=$(grep -rnE '"session_meta"[[:space:]]*=>' crates/ --include='*.rs' || true)
unexpected=$(echo "$violators" | grep -v 'crates/claudepot-core/src/codex_session/' || true)
if [ -n "$unexpected" ]; then
echo "::error::Codex rollout decoder found outside codex_session/:"
echo "$unexpected"
echo
echo "Codex rollout parsing (matching on 'session_meta' record type)"
echo "must live in claudepot-core/src/codex_session/. Duplicating it"
echo "elsewhere leads to two parsers drifting in parallel — exactly"
echo "what the plan's leaf-module discipline prevents."
exit 1
fi
- name: Invariant — raw text SELECTs are confined to redaction-aware paths
# R9 of the plan: every read of transcript-content columns
# must go through redaction::apply before crossing an
# emission boundary. This grep catches new SELECTs that
# skip the redaction step.
run: |
set -euo pipefail
patterns='SELECT.*user_text|SELECT.*assistant_text|SELECT.*tool_result_text'
violators=$(grep -rlE "$patterns" crates/ --include='*.rs' || true)
unexpected=$(echo "$violators" \
| grep -v 'crates/claudepot-core/src/shared_memory/search.rs' \
| grep -v 'crates/claudepot-core/src/shared_memory/read.rs' \
| grep -v 'crates/claudepot-core/src/shared_memory/indexer.rs' \
| grep -v 'crates/claudepot-core/src/shared_memory/schema.rs' \
| grep -v 'crates/claudepot-core/src/shared_memory/claude_exchanges.rs' \
|| true)
if [ -n "$unexpected" ]; then
echo "::error::Raw text SELECTs found outside redaction-aware paths:"
echo "$unexpected"
echo
echo "Every emission of exchanges.user_text / assistant_text /"
echo "tool_calls.tool_result_text must go through redaction::apply"
echo "before crossing a UI / export / log / MCP boundary."
echo "Extend the allowlist in .github/workflows/ci.yml if adding"
echo "a legitimate reader, and document why redaction is handled"
echo "at the call site."
exit 1
fi
- name: Invariant — no bare matches!() statements
# H2 of the grill report: a bare matches!(err, Variant);
# returns bool and discards it. Tests pass against any
# error variant. The grill round caught six such sites;
# this gate prevents regression.
#
# The signal is a `matches!()` call used as a *statement*
# (followed by `;`), not as an expression value. Match
# arms, return values, and let bindings have no trailing
# semicolon and are correctly excluded.
run: |
set -euo pipefail
violators=$(grep -rnE '^[[:space:]]+matches!\(.*\);' crates/ --include='*.rs' || true)
if [ -n "$violators" ]; then
echo "::error::Bare matches!() statements found:"
echo "$violators"
echo
echo "A bare matches!() returns bool and discards it — the test"
echo "passes against any variant. Wrap in assert!(matches!(...))."
exit 1
fi
test:
name: Tests (${{ matrix.os }})
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v6
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
# See the lint job for why rust-cache replaces actions/cache.
- name: Rust build cache
uses: Swatinem/rust-cache@v2
- name: Tests (core + cli)
run: cargo test -p claudepot-core -p claudepot-cli
# The tauri crate carries real unit tests (orchestrators, DTOs,
# preferences) plus an install smoke test that no other gate
# runs. It builds without extra system packages on macOS and
# Windows; on Linux it needs webkit2gtk + friends (see
# release.yml's build-linux-x86_64 apt list), which aren't
# worth installing on every CI run — release.yml's Linux build
# job is the Linux compile gate for this crate.
#
# tauri-build validates the `bundle.externalBin` entry at
# build-script time, so the CLI binary must be staged under
# src-tauri/binaries/ first (a fresh checkout doesn't have it).
# The frontend `dist/` is NOT needed: dev-profile codegen
# doesn't embed assets.
- name: Tests (tauri crate)
if: matrix.os != 'ubuntu-latest'
shell: bash
run: |
set -eu
cargo build -p claudepot-cli
TRIPLE=$(rustc -vV | sed -n 's/^host: //p')
EXT=""
case "$TRIPLE" in *windows*) EXT=".exe" ;; esac
mkdir -p src-tauri/binaries
cp "target/debug/claudepot$EXT" "src-tauri/binaries/claudepot-cli-$TRIPLE$EXT"
cargo test -p claudepot-tauri
# grill X26: `--ignored` integration tests are normally locked out
# of the default suite — they touch the real OS scheduler, hit
# live LLMs, or otherwise depend on infra that the matrix runners
# can't always reach. Without a lane in CI they ran nowhere, so
# the register→fire→record→prune path and the templates+LLM
# round-trip drifted unobserved.
#
# This job runs them in advisory mode: `continue-on-error: true`
# so a flake doesn't redden the merge gate, but failures are
# still visible on the Actions summary page. Promote to a hard
# gate once the infra-flakes are smoothed out.
e2e-ignored:
name: E2E (--ignored, advisory)
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v6
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
# See the lint job for why rust-cache replaces actions/cache.
- name: Rust build cache
uses: Swatinem/rust-cache@v2
# Scheduler-touching E2E. On Linux the active scheduler is
# systemd-user; tests that require it self-skip when systemd
# isn't reachable inside the runner sandbox, so the lane stays
# green even when systemd is absent.
- name: Agent E2E (--ignored)
run: cargo test -p claudepot-core --test agent_e2e -- --ignored
# Templates + LLM round-trip. Self-skips when no API key is
# available (no secret on PRs from forks — the lane stays
# advisory either way).
- name: Templates real-LLM (--ignored)
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: cargo test -p claudepot-core --test templates_real_llm -- --ignored
frontend-test:
name: Frontend tests (Vitest)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Setup pnpm
uses: pnpm/action-setup@v5
with:
version: 9
- name: Setup Node
uses: actions/setup-node@v5
with:
node-version: 24
cache: pnpm
- name: Install JS deps
run: pnpm install --frozen-lockfile
- name: Typecheck
run: pnpm tsc --noEmit
# The production vite build catches failure classes tsc and
# vitest don't (asset resolution, CSS shard syntax errors,
# rollup-only module issues). Without this step the first
# `pnpm build` is tauri.conf.json's beforeBuildCommand at
# release-tag time — the most expensive place to find out.
# Unlike web/'s skipped build (see ci-web.yml), this is a
# plain client-side vite bundle that builds offline.
- name: Production build (vite)
run: pnpm build
- name: Run tests
run: pnpm test