Skip to content
Back to skills

Source 372190a03ab0a8fc49e52251

ASecurity

Use when testing VCL against real Fastly edge infrastructure, writing assertion-based Fiddle tests, producing shareable fiddle URLs for bug reproductions, running VCL integration tests in CI, linting VCL remotely via the Fiddle API, or working with clientFetch/events/originFetches test expressions.

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added November 7, 2026
testingrustgoshellbashnodeexpresstestinggitapi

Works with

  • cli
  • api

Security analysis

A100/100

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

Scanned November 7, 2026

npx -y skills add gutsofpratik/OpenClaude-Skills --skill source-372190a03ab0a8fc49e52251 --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Source 372190a03ab0a8fc49e52251?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Source 372190a03ab0a8fc49e52251
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/gutsofpratik-source-372190a03ab0a8fc49e52251/badge)](https://www.skillsdirectory.com/skills/gutsofpratik-source-372190a03ab0a8fc49e52251)

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

Download with Pro
SKILL.md
---
name: fastly-fiddle
description: "Use when testing VCL against real Fastly edge infrastructure, writing assertion-based Fiddle tests, producing shareable fiddle URLs for bug reproductions, running VCL integration tests in CI, linting VCL remotely via the Fiddle API, or working with clientFetch/events/originFetches test expressions."
---

# Fastly Fiddle — Real-Edge VCL Testing

## Trigger and scope

Trigger on: Fastly Fiddle, fiddle.fastly.dev URLs, the Fiddle HTTP API, CI testing of VCL services, real-edge VCL tests, shareable VCL reproductions, `clientFetch.*`/`events.*`/`originFetches.*` test expressions, `.test.js` / Mocha specs that target fiddles, SSE `updateResult` / `waitingForSync` events, remote VCL linting via Fiddle, or validating real Fastly behavior (geo, WAF, ESI, clustering, shielding) that local tools cannot simulate.

Do NOT use for: local VCL unit testing (use `falco`), fast TDD loops (Fiddle has a 10-20s edge-sync floor per publish), Fastly Compute/Wasm testing (use `viceroy` or `fastlike`), production service deployment (use `fastly-cli`), or anything requiring authenticated Fastly API access — Fiddle does not use your Fastly API key.

Fastly Fiddle is a web-based sandbox at <https://fiddle.fastly.dev> that compiles and runs VCL on real Fastly edge nodes. Because it uses the production VCL compiler and real POPs, it's the only way outside a real service to test VCL features that depend on edge infrastructure — geolocation data, WAF, ESI, clustering, shielding, rate limiting, real TLS, and real cache behavior.

**Official UI**: <https://fiddle.fastly.dev>
**Demo CI runner**: <https://github.com/fastly/demo-fiddle-ci>
**API base**: `https://fiddle.fastly.dev` (undocumented but stable; no auth required for public fiddles)

## Prerequisites and public uploads

The helper requires Bash, `curl` 7.76 or newer, `jq`, and network access to `fiddle.fastly.dev`.
If a tool is missing, name it and provide installation guidance before running the helper.

Creating or updating a fiddle uploads its VCL, origins, request headers, request bodies, and tests to a public service, including with `--lint-only`.
Only publish when the user has authorized a public Fiddle upload or shareable reproduction.
For a request limited to local testing, use Falco and keep the spec local.
Never include credentials or private project data in a fiddle.

Set `FIDDLE_SKILL_DIR` to the absolute directory containing this `SKILL.md`, using the installed skill location supplied by the client:

```bash
FIDDLE_SKILL_DIR=/absolute/path/to/fastly-fiddle
```

This is a variable you assign, not a client-provided environment variable.
Keep the user's project as the working directory so inputs such as `fiddle.json` and `spec.json` remain project-relative.
Bundled scripts and examples use `FIDDLE_SKILL_DIR` instead.

## When Fiddle, when Falco

| Need                                                  | Use                                |
| ----------------------------------------------------- | ---------------------------------- |
| Fast local iteration (< 1s), watch mode, offline      | `falco test`                       |
| Real Fastly VCL compiler and semantics                | Fiddle                             |
| Real `client.geo.*`, WAF, ESI, rate limiting, shield  | Fiddle                             |
| Shareable URL for bug repros and support tickets      | Fiddle                             |
| CI against real edge nodes                            | Fiddle                             |
| Structured lint with line/col (no execution required) | Either                             |
| Fastly Compute (WASM)                                 | Neither — use `viceroy`/`fastlike` |

Common workflow: iterate locally with `falco test` for speed, then push edge cases to Fiddle when you need real Fastly behavior or a shareable link. See [falco-vs-fiddle.md](references/falco-vs-fiddle.md) for full trade-offs.

## Workflow: deliverable first, then the cheapest check that answers your question

Two rules keep you out of the slow path, which is what wastes time and gets
agents killed by a wall-clock limit:

1. **If your job is to produce a spec file, write it to disk first**, before
   any network call. The file is the deliverable — it must exist even if a
   later publish stalls on a cold edge-sync. Don't build the spec only inside
   a `curl --data` argument and lose it when the call blocks.

2. **Match the check to the question.** "Does this VCL compile / is the spec
   shape accepted?" is answered by a _single_ `POST /fiddle` reading `valid` —
   ~1-3s, no execution, no edge-sync wait (see gotcha #5). You only need the
   full publish → execute → SSE round trip when you must observe _runtime_
   assertion results (actual `clientFetch`/`originFetches`/`events` values),
   and that pays the 10-120s edge-sync floor per publish. **Executing is the
   exception, not the default** — reach for it deliberately, and always bound
   your wait; never block indefinitely on the stream.

### Lint-only (the common case): compile check, no execution

```bash
bash "$FIDDLE_SKILL_DIR/scripts/run-fiddle.sh" --lint-only fiddle.json
# {fiddle_id, url, valid, lintStatus}. Exit 0 = compiles, 2 = lint error
# (details on stderr). No /execute, no SSE, no edge-sync wait.
```

The raw equivalent is one call — POST and read `valid`:

```bash
UA='fiddle-skill-example/1.0'
curl -sS --max-time 30 -X POST https://fiddle.fastly.dev/fiddle \
  -H 'Content-Type: application/json' -H "User-Agent: $UA" \
  --data @fiddle.json | jq '{valid, lintStatus}'   # valid==true ⇒ it compiles
```

### Full round trip (only when you need runtime results)

When you genuinely need to see assertions pass/fail on the edge, the bundled
helper handles publish → execute → SSE → completion-detection in one call,
with a bounded `--max-wait` (default 180s per attempt) so it can't hang:

```bash
bash "$FIDDLE_SKILL_DIR/scripts/run-fiddle.sh" "$FIDDLE_SKILL_DIR/examples/robots.json"
# Prints fiddle URL, then pass/fail JSON per assertion (including body_preview
# and status by default). Exits non-zero on failure. Pass --no-bodies for
# compact output.

# Iterate against an already-published fiddle without paying edge-sync again:
bash "$FIDDLE_SKILL_DIR/scripts/run-fiddle.sh" --id <fiddle-id>           # re-execute (warmest, ~2s)
bash "$FIDDLE_SKILL_DIR/scripts/run-fiddle.sh" --id <fiddle-id> spec.json # PUT then execute
```

The equivalent raw-curl flow, for reference or when the helper isn't available:

```bash
UA='fiddle-skill-example/1.0'

# 1. Create. Capture the ID and the validity flag.
RESP=$(curl -sS -X POST https://fiddle.fastly.dev/fiddle \
  -H 'Content-Type: application/json' -H 'Accept: application/json' \
  -H "User-Agent: $UA" \
  --data '{
    "origins": ["https://http-me.fastly.dev"],
    "vcl": {
      "init": "# Synthetic robots.txt response\n# via error restart pattern",
      "recv": "if (req.url.path == \"/robots.txt\") {\n  error 601;\n}",
      "error": "if (obj.status == 601) {\n  set obj.status = 200;\n  synthetic {\"User-agent: BadBot\"};\n  return(deliver);\n}"
    },
    "requests": [
      { "path": "/robots.txt",
        "headers": "X-Custom: value\nX-Other: second",
        "tests": ["clientFetch.status is 200", "clientFetch.bodyPreview includes \"BadBot\""] }
    ]
  }')
FID=$(echo "$RESP" | jq -r '.fiddle.id')
echo "$RESP" | jq '{valid, lintStatus}'   # bail here if valid is false

# 2. Execute. Subscribe to the SSE stream IMMEDIATELY — session IDs expire fast.
SID=$(curl -sS -X POST "https://fiddle.fastly.dev/fiddle/$FID/execute?cacheID=1" \
        -H 'Accept: application/json' -H "User-Agent: $UA" | jq -r '.sessionID')

# 3. Stream results. Emits repeated `event: waitingForSync` (~10-20s on first
#    publish) then `event: updateResult` with full pass/fail data. ALWAYS cap
#    the stream with --max-time so a slow/cold sync can't block you forever;
#    on timeout, re-execute the same $FID (warm, ~2s) rather than re-publishing.
curl -sS -N --max-time 120 -H "User-Agent: $UA" "https://fiddle.fastly.dev/results/$SID/stream"
```

Full protocol details in [api.md](references/api.md).

## Wire-format gotchas

Non-obvious behavior that will break tools round-tripping fiddles programmatically:

1. **`vcl` on input, `src` on output.** You `POST {"vcl": {"recv": "..."}}` but `GET` returns `{"src": {"recv": "..."}}`. The server renames the key on normalization. Any tool that fetches a fiddle and re-publishes it must map `src` → `vcl` (or send `src` — both work on input). **Update existing fiddles with `PUT /fiddle/:id`** — same body shape as `POST`, but partial updates are not supported; omitted subroutines are cleared.

2. **`tests` is a string on the wire.** You can send `tests: ["a", "b"]` but a subsequent `GET` returns `tests: "a\nb"`. One assertion per line. Split on `\n` when reading.

3. **`headers` is a newline-joined string, not an array.** Unlike `tests` (which accepts both), `headers` must be a string: `"headers": "User-Agent: BadBot/1.0\nX-Custom: value"`. An array will be rejected with a validation error.

4. **Request fields are auto-defaulted by the server.** A `GET` of a fiddle you just created will include fields you didn't send: `method: "GET"`, `connType: "h2"` (HTTP/2 by default — matters for tests that depend on protocol), `enableCluster: true`, `enableShield: false`, `useFreshCache: false`, `sourceIP: "client"`, `followRedirects: false`, `delay: 0`. Set them explicitly if you care.

5. **Invalid VCL still gets a fiddle ID.** `POST` returns `{valid: false, lintStatus: {...}, fiddle: {id, ...}}` for broken VCL. On a **create/update** (`POST`/`PUT`) response, `valid` is the lint result — `true` if the VCL compiles, `false` if not, with details in `lintStatus`. That's the number to trust for "does this compile?", and you get it without executing anything. Don't rely on HTTP status.

    **But `valid` means something different on a `GET`.** There it tracks execution, not compilation: it stays `false` until the fiddle has been executed at least once, then flips to `true`. A fiddle that lints perfectly cleanly still reads back as `valid: false` right after you create it. So judge compilation from the create/update response (or by re-submitting the spec) — never from a `GET`. A `GET` can't distinguish a valid-but-not-yet-run fiddle from a genuinely broken one: both come back `valid: false` with an empty `lintStatus`.

6. **VCL string concat with `+` rejects parenthesized operands.** `set X = "used=" + (a - b);` fails with a _misleading_ "Remove the trailing `+` operator" suggestion — the `+` is fine, the `(` is what the parser rejects. Compute the sub-expression into a local variable first. See [spec-shape.md](references/spec-shape.md#vcl-string-concatenation).

7. **`error 8NN;` / `error 9NN;` is rejected by Fiddle lint — use 6xx.** Any 800–999 code fails with "8xx and 9xx error codes are used internally by Fastly. Use 6xx instead.", in any subroutine and any context (bare or inside `if`/`else`/`switch`). So the classic `error 801 <url>;` redirect idiom trips Fiddle lint even inside an `if` — use `error 602 "<url>";` and build the 301 in `vcl_error`. Codes 400–799 are accepted. See [spec-shape.md](references/spec-shape.md#error-codes-use-6xx-for-synthetics).

8. **Some test expressions have built-in delays.** `originFetches.count() is 0` returns `asyncDelay: 2500` — the server waits 2.5s before evaluating "did nothing happen?". Client wait time must accommodate this; 45-60s is a safe ceiling.

9. **SSE session IDs are short-lived.** Subscribe to `/results/<sessionID>/stream` within seconds of receiving the ID from `/execute`. Delayed connections get a 404. Always have the stream open before you start waiting on results.

10. **Test DSL has unusual syntax.** No `.first()`, no `reqHeaderValue()`, no `isnt`/`empty` operators. Event objects expose only `url`/`method`/`return`/`status`/`ttl` — not arbitrary `req.http.*` values. **Read [test-dsl.md](references/test-dsl.md) before writing assertions.**

11. **The server assigns a fresh fiddle ID on every `POST /fiddle`** — even for byte-identical input. IDs are not a content hash; back-to-back POSTs of the same body return different IDs, and each new ID needs its own edge-sync pass before `/execute` can produce results. **`PUT /fiddle/<id>`** keeps the URL stable (good for shared bug-repro links) but the new VCL still recompiles and propagates, so PUTs pay the same edge-sync cost as POSTs. The genuinely warm path is **re-executing an unchanged fiddle ID** — same content, repeat `/execute` calls finish in ~2s. Capture the ID from the first POST and reuse it; vary `cacheID` to force cold caches without re-publishing. (PUT is not partial — omitted subroutines are cleared, see #1.)

12. **`originFetches.count() is N` is fragile under retries and shared `cacheID`.** A retry — automatic in `run-fiddle.sh`, or manual via `--id <fid>` — re-executes against the same `cacheID`, so any origin response cached on the previous attempt is now a HIT and `originFetches.count()` drops to 0. Two reliable fixes: set **`useFreshCache: true`** on the request (forces a fresh cache, ignoring the session `cacheID` — see [spec-shape.md `Request objects`](references/spec-shape.md#request-objects)), or assert via **`events.where(fnName=fetch).count()`** (counts subroutine entries, not network calls). The same goes for `originFetches[0].*` assertions whenever the test runs after a possible warmup.

## Authoring conventions

Fiddles are read by humans in a browser. These aren't surprises, but they make shared fiddles useful:

- **Set a `title`.** Makes fiddles findable in browser tabs, bookmarks, and shared links. Example: `"title": "fastly_info.state: compound values deep dive"`.
- **Use `init` as a header comment.** The `init` subroutine renders first in the UI. Put a short summary there (~55 chars/line): `"init": "# fastly_info.state: compound values\n# Demonstrates MISS-CLUSTER, HIT-CLUSTER, HIT-SYNTH"`.
- **Format VCL with `\n` and indentation** rather than cramming everything onto one line.
- **Send `User-Agent: <tool>/<version>` on every API call.** Fiddle is unauthenticated shared infra; default `curl/x.y` or library UAs are bad citizenship. This is the API call's UA, not the simulated request's `headers`.

## Testing in CI

Reference implementation: [fastly/demo-fiddle-ci](https://github.com/fastly/demo-fiddle-ci) — a Node + Mocha harness. Clone it and write your `{spec, scenarios[]}`.

The one non-obvious thing: it publishes the fiddle once, then re-executes the same fiddle ID per scenario with different `requests[]`. Only re-execution is warm (~2s); a fresh publish pays the 10-20s edge-sync floor (see "Limits" below and gotcha #11). Keep scenarios sequential.

## References

| Topic               | File                                                | Use when...                                                        |
| ------------------- | --------------------------------------------------- | ------------------------------------------------------------------ |
| **Helper script**   | [scripts/run-fiddle.sh](scripts/run-fiddle.sh)      | Publishing + executing + streaming a fiddle in one shell command   |
| **Example payload** | [examples/robots.json](examples/robots.json)        | Starting from a known-good minimal fiddle spec                     |
| **HTTP API**        | [api.md](references/api.md)                         | Calling Fiddle endpoints directly, driving it from any language    |
| Fiddle spec shape   | [spec-shape.md](references/spec-shape.md)           | Building the JSON payload: origins, src, requests, defaults        |
| Test DSL            | [test-dsl.md](references/test-dsl.md)               | Writing `clientFetch.*`, `events.where(...)`, `originFetches.*`    |
| Falco vs Fiddle     | [falco-vs-fiddle.md](references/falco-vs-fiddle.md) | Choosing the right tool, or combining them in one workflow         |

## Limits and cautions

- **No auth required, no quota documented.** Be a good citizen: don't hammer the API in tight loops, and always send a descriptive `User-Agent` on API calls (see Authoring conventions). Use `cacheID` consistently across requests that need to share cache, and vary it to force cold caches.
- **Edge-sync floor is ~10-20s per publish, sometimes much longer.** Applies per fiddle ID, not per unique content — and every `POST /fiddle` mints a new ID (see Wire-format gotchas #11), so re-publishing identical input still pays the full sync cost. Cold publishes regularly take 60-120s in practice. Unusable for TDD. Batch changes; execute once per meaningful delta; reuse IDs via `PUT` when iterating.
- **Execution hops through a real POP** (tests observed running from IAD on node `kiad7000140`). Geographic assertions reflect wherever the fiddle executor landed.
- **Fiddles are public by default.** Don't put secrets in VCL you publish.
- **The API is undocumented.** Field names and behavior can change.

Files in this skill

  • PROVENANCE.json211 B
  • SKILL.md16.7 KB
  • UPSTREAM-LICENSE.txt1 KB
  • examples/robots.json668 B
  • references/api.md8.2 KB
  • references/falco-vs-fiddle.md3.9 KB
  • references/spec-shape.md12.6 KB
  • references/test-dsl.md8.5 KB
  • scripts/run-fiddle.sh26.8 KB

Attribution

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

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.