Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsBlogPro
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Authors
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges
  • Chrome Extension
  • Skill Manager

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

ProTermsPrivacyRefunds
Back to skills

Run Shunt

ASecurity

Build, launch, and drive shunt — the Claude Code LLM gateway (a Rust/axum Anthropic-Messages proxy). Use to run, start, smoke-test, or curl-drive the gateway, exercise /v1/models discovery and /v1/messages proxying, or connect Claude Code to a local shunt instance.

241 stars
0 votes
0 copies
1 views
Added 9/19/2026
ai-agentspythonrustgoshellbashgitapi

Works with

claude codecliapi

Security Analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned 9/23/2026

$npx -y skills add pleaseai/shunt --skill run-shunt --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Run Shunt?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Run Shunt
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/pleaseai-run-shunt/badge)](https://www.skillsdirectory.com/skills/pleaseai-run-shunt)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
Files
SKILL.md
---
name: run-shunt
description: Build, launch, and drive shunt — the Claude Code LLM gateway (a Rust/axum Anthropic-Messages proxy). Use to run, start, smoke-test, or curl-drive the gateway, exercise /v1/models discovery and /v1/messages proxying, or connect Claude Code to a local shunt instance.
---

# Run shunt

`shunt` is a Rust ([axum](https://github.com/tokio-rs/axum)) HTTP server — a Claude Code LLM gateway. It has **no GUI**: you drive it with `curl`. It listens on `127.0.0.1:3001` by default and serves an Anthropic-Messages surface (`GET /v1/models`, `POST /v1/messages`, `POST /v1/messages/count_tokens`, `HEAD /`). For each mapped `model` id it diverts inference to another provider (OpenAI / Codex / ChatGPT via the OpenAI Responses API); everything else passes through to Anthropic.

**Primary agent path:** run the committed driver [`.claude/skills/run-shunt/smoke.sh`](smoke.sh). It builds the binary, spins up a local mock upstream (so no real API key is needed), launches the gateway, and drives every route end to end with assertions. That is the way to confirm a change works.

All paths below are relative to the repo root (the `shunt/` directory).

## Prerequisites

Everything the driver needs is already standard on macOS/Linux dev boxes:

```bash
cargo --version     # Rust stable (built with 1.94); toolchain via rust-toolchain.toml if present
python3 --version   # smoke.sh uses http.server as a stand-in upstream
curl --version
jq --version        # smoke.sh asserts JSON responses with jq
lsof -v
```

On a bare Ubuntu container: `apt-get install -y curl jq lsof python3` and install Rust via `rustup` if `cargo` is missing.

## Build

```bash
BIN="$(cargo build --locked --message-format=json-render-diagnostics | jq -sr '[.[] | select(.reason == "compiler-artifact" and .target.name == "shunt" and (.target.kind | index("bin"))) | .executable | select(. != null)] | last // empty')"
test -x "$BIN"
```

`BIN` resolves Cargo's actual executable path, including `CARGO_TARGET_DIR` and a configured target triple.

## Run (agent path) — the smoke driver

This is what you run to see shunt working. It is hermetic (no network, no credentials) and exits non-zero on the first failed assertion.

```bash
.claude/skills/run-shunt/smoke.sh
```

Expected tail:

```
  PASS shunt check -> config ok
  PASS HEAD / -> 200 (server live)
  PASS GET /v1/models returns configured model
  PASS POST /v1/messages proxied to upstream and returned its body
  PASS malformed request -> 400 invalid_request_error
  PASS GET /health reports ok
  PASS GET /protocol describes the anthropic-messages contract
  PASS GET /routes resolves the configured route
  PASS POST /v1/messages/count_tokens proxied to upstream on its own path
All smoke checks passed.
```

What it covers: every route the server registers unconditionally. Config validation (`shunt check`), liveness (`HEAD /`), health (`GET /health`), the gateway contract (`GET /protocol`), model discovery (`GET /v1/models`), the resolved route table (`GET /routes`), both proxy forward paths (`POST /v1/messages` and `POST /v1/messages/count_tokens`, routed to a local mock that stands in for `api.anthropic.com` and asserted by the path it forwarded to), and the routing error path (a body with no `model` field → `400 invalid_request_error`). Routes that only an optional config section registers (the Codex endpoint, the usage surfaces) are out of scope, since the smoke config does not enable them.

`SHUNT_PORT` and `MOCK_PORT` default to `31711` and `31712`. Set either to `0` to bind an ephemeral port instead, which is the conflict-free way to run the driver beside a live gateway.

The driver has its own regression, [`test_smoke.sh`](test_smoke.sh). Run it after editing `smoke.sh`. It proves the driver refuses a port outside `0..65535` by name; refuses a port another listener already holds, where the real process dies at bind and the driver says which one died; and refuses a shunt listening on a port it never requested, which is the case the pid-derived readiness check catches. It then runs the normal path. The stand-in listener answers every assertion the driver makes, so a driver that trusted `$SHUNT_PORT` over the process it started would go green against it and get caught.

### Drive it by hand

Write a config, launch, and curl it yourself:

```bash
BIN="$(cargo build --locked --message-format=json-render-diagnostics | jq -sr '[.[] | select(.reason == "compiler-artifact" and .target.name == "shunt" and (.target.kind | index("bin"))) | .executable | select(. != null)] | last // empty')"
test -x "$BIN"
"$BIN" run --config ./shunt.toml    # or copy shunt.toml.example first
```

Then, against the running server:

```bash
curl -s "http://127.0.0.1:3001/v1/models?limit=1000" | jq .
# => {"data":[{"id":"claude-opus-via-codex","display_name":"Opus (via Codex)"}]}
```

Validate a config without starting the server:

```bash
BIN="$(cargo build --locked --message-format=json-render-diagnostics | jq -sr '[.[] | select(.reason == "compiler-artifact" and .target.name == "shunt" and (.target.kind | index("bin"))) | .executable | select(. != null)] | last // empty')"
test -x "$BIN"
"$BIN" check --config ./shunt.toml   # prints "config ok" or a precise error
```

CLI shape: `shunt run|check [--config <path>]` (also `shunt --check`). Default config path is `./shunt.toml`; `SHUNT_`-prefixed env vars override (with `__` for nesting).

## Direct invocation (internal logic — most PRs touch this)

The interesting code is the **Anthropic Messages ⇄ OpenAI Responses translation** (`src/adapters/`, `src/model/`) and routing (`src/routing.rs`). These are covered by unit + integration tests — the fastest inner loop for a PR touching them:

```bash
cargo test --workspace                              # all tests
cargo test --test responses_translate               # the translation integration suite
```

Full pre-PR gate (matches CI):

```bash
cargo fmt --all --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all-features --workspace
```

## Run (human path) — connect Claude Code to a local shunt

Point Claude Code at the running gateway. shunt does **not** validate the credential, but Claude Code still needs one set or it drops to its login wizard. Per the [gateway-connect docs](https://code.claude.com/docs/en/llm-gateway-connect):

```bash
export ANTHROPIC_BASE_URL=http://127.0.0.1:3001
export ANTHROPIC_AUTH_TOKEN=local-dummy        # any string; shunt ignores it
export CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1   # opt-in: pull /v1/models into the picker
export ANTHROPIC_CUSTOM_MODEL_OPTION=gpt-5.2-codex    # add a non-claude id to /model (see Gotchas)
claude    # started from the same shell; /status shows the base URL
```

Verify the wiring without opening Claude Code (this is the docs' own check):

```bash
curl -X POST "$ANTHROPIC_BASE_URL/v1/messages" \
  -H "Authorization: Bearer $ANTHROPIC_AUTH_TOKEN" \
  -H "anthropic-version: 2023-06-01" -H "content-type: application/json" \
  -d '{"model":"claude-opus-via-codex","max_tokens":1,"messages":[{"role":"user","content":"."}]}'
```

With a default (anthropic-routed) config this forwards to `api.anthropic.com` and needs a real Anthropic key in the header to get a `200`; a mapped model routed to `openai`/`codex` needs that provider's credential instead. For a credential-free run, use `smoke.sh` (mock upstream) rather than this path.

## Gotchas

- **No GUI, no `npm start`.** It's a Rust HTTP server. "Running it" means launch + `curl`. The driver is the smoke script.
- **Model discovery drops non-`claude`/`anthropic` ids.** Claude Code's `/v1/models` importer ignores any `id` not starting with `claude` or `anthropic`, so to route to e.g. `gpt-5.2-codex` you either alias it under a `claude…` discovery id **or** add it via `ANTHROPIC_CUSTOM_MODEL_OPTION` (the primary way). This is a Claude Code constraint, not a shunt bug.
- **shunt ignores the request credential.** `GET /v1/models` reads `authorization`/`x-api-key` but discards it (`src/discovery.rs`), and the proxy just forwards headers upstream. So any dummy token works for local driving — the real key only matters to the *upstream* provider.
- **`shunt check` is strict about provider shape.** `providers.openai.adapter` and `providers.codex.adapter` must be `"responses"`; `openai.auth` must be `api_key`, `codex.auth` must be `chatgpt_oauth` — otherwise `check` fails with a specific error (`src/config.rs`). Partial TOML is fine: figment merges your file over built-in defaults, so you only need to specify what differs.
- **A discovery `[[models]]` entry with no matching `[[routes]]`** logs a `WARN` at startup/`check` but is not fatal.
- **`GET /protocol` is the machine-readable gateway contract.** It is unauthenticated and reports shunt's package version, Anthropic-Messages format, supported endpoints, header handling, attribution behavior, and model-discovery constraints.
- **zsh quoting:** quote URLs containing `?` (globbing) and mind `noclobber` on `>` redirects when driving by hand in this repo's shell.

## Troubleshooting

- **`smoke.sh` reports `shunt exited during startup` or `mock upstream exited during startup`**: the dumped log says `Address already in use`, so a stale process holds that test port. Run `lsof -nP -iTCP:${MOCK_PORT:-31712} -sTCP:LISTEN` for the mock port or replace it with `${SHUNT_PORT:-31711}` for the gateway port. Stop the reported PID, then re-run. `SHUNT_PORT=0 MOCK_PORT=0` skips the conflict outright by binding ephemeral ports.
- **`502 Bad Gateway` / `api_error: error sending request for url (...)`** — shunt reached routing but the upstream `base_url` was unreachable (wrong host/port, or the mock/provider isn't up). This is the correct error mapping (`src/error.rs`), not a crash. Check the target `base_url`.
- **`400 invalid_request_error: request body must include a JSON model field`** — the request body isn't JSON with a `model` key. Routing happens before forwarding (`src/routing.rs`).
- **`cargo clippy` fails the build** — CI sets `RUSTFLAGS=-D warnings`; warnings are errors. Fix them before a PR (`cargo clippy --all-targets --all-features -- -D warnings`).

Attribution

pleaseaipleaseai
View sourceSee grades on GitHubMore from pleaseai →
SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Related Skills

Caveman

Terse caveman voice: answer first, fluff gone, every technical fact kept. Use for /caveman, "caveman mode", "talk like caveman", "be brief", "less tokens". Stays on until "stop caveman" or "normal mode".

1100021 votes

Hyperplan

Adversarial multi-agent planning skill. Self-orchestrates 5 hostile category members (unspecified-low, unspecified-high, deep, ultrabrain, artistry) via team-mode for ruthless cross-critique debate, distills only the defensible insights, then MANDATORILY hands the distilled insight bundle to the `plan` agent for executable plan formalization. Use when planning needs maximum rigor and surfacing of weak assumptions, blind spots, and over-engineering. Triggers: 'hyperplan', 'hpp', '/hyperplan', ...

698431 votes

Writing Skills

Create and manage Claude Code skills in HASH repository following Anthropic best practices. Use when creating new skills, modifying skill-rules.json, understanding trigger patterns, working with hooks, debugging skill activation, or implementing progressive disclosure. Covers skill structure, YAML frontmatter, trigger types (keywords, intent patterns), UserPromptSubmit hook, and the 500-line rule. Includes validation and debugging with SKILL_DEBUG. Examples include rust-error-stack, cargo-dep...

3931 votes

Mcp Code Execution

Routes multi-tool workflows through MCP servers for large datasets and pipelines. Use when Bash tool overhead is limiting throughput on data-heavy tasks.

3421 votes

catchup

Recovers the conversation and failed tool calls of a previous Codex, Amp, Claude Code, Antigravity, Cline, Copilot CLI, Cursor, DeepSeek Harness, Grok Build, Kimi, OpenCode, Pi Agent, or ZCode session. Use when the user says "catch up", "what did the last session do", "get me up to speed", "I switched agents", asks to recover/summarize a previous session before continuing, or asks to diagnose or report a catchup failure. Do NOT use for the current conversation, git history, or any non-agent log.

741 votes
View all in ai-agents →