Skip to content

Add commands to check and run VCL code - #1964

Open
jedisct1 wants to merge 13 commits into
mainfrom
fdenis/fiddle
Open

jedisct1 wants to merge 13 commits into
mainfrom
fdenis/fiddle

Conversation

@jedisct1

@jedisct1 jedisct1 commented Nov 7, 2026 •

Copy link
Copy Markdown
Contributor

Change summary

This adds three commands to test VCL code on real Fastly servers: fastly vcl check, fastly vcl run, and fastly vcl serve.

To test on Fastly servers, there's Fiddle, but using it programmatically is currently difficult.

So this integrates VCL execution in the CLI. Fiddle is used under the hood. But the commands talk about VCL files, origins, and requests, so that Fiddle can be renamed, get a different interface, and an option to use a local engine can be added later without breaking anyone.

I've been using the CLI with these commands for the past 4 months, and these commands drastically improve the user experience when developing applications using VCL.

New commands:

  • vcl check compiles VCL with the real Fastly compiler and prints problems as file:line: level: message, so your editor can jump straight to them.

  • vcl run sends one request through your VCL on an edge node, then shows the status, content type, size, cache result, and body. Add --verbose for the subroutine trace and origin fetches, or --json for scripts.

  • vcl serve starts a local HTTP server on 127.0.0.1:7676 that proxies every request to your VCL on the edge. Point curl, a browser, or your test suite at it. --watch redeploys when a file changes, and keeps serving the old VCL until the new one is live.

For example:

$ fastly vcl check recv.vcl error.vcl deliver.vcl
SUCCESS: VCL compiled cleanly.
$ fastly vcl run recv.vcl error.vcl deliver.vcl --path /old-blog
GET /old-blog → 301 (0 bytes, HIT)
$ fastly vcl serve recv.vcl error.vcl deliver.vcl
Serving VCL on http://127.0.0.1:7676 (Ctrl-C to stop)

Each VCL file holds a subroutine body.

You can name files after their subroutine (recv.vcl, fetch.vcl) or pass them with flags like --recv. If a file still has the sub vcl_recv { ... } wrapper, you get a clear error telling you to remove it.

You can repeat --origin up to five times. Origins show up in the VCL as backends F_origin_0 to F_origin_4, and the first one is the default. Leave it out, and requests go to https://http-me.fastly.dev, which just echoes them back.

run also takes the same request options as the Fiddle UI:

  • --path, --method, --header (-H), and --body (prefix it with @ to read a file)
  • --client-from, to make the request come from a country like germany or japan (or from Fiddle's client and server locations), for testing geolocation
  • --connection, for h2, h1, or plain http
  • --shield, --no-cluster, --follow-redirects, and --fresh-cache

The CLI remembers the sandbox it created, along with a cache bucket, in vcl-sandbox.json next to its config file.

So the next run updates that sandbox in seconds instead of waiting for a new one to reach the edge, and running the same request twice shows a MISS turning into a HIT.

serve gets its own sandbox, so a run in another terminal can't swap its VCL out from under it. If the edge drops an idle sandbox, serve redeploys and retries.

All Submissions:

  • Have you followed the guidelines in our Contributing document?
  • Have you checked to ensure there aren't other open Pull Requests for the same update/change?

New Feature Submissions:

  • Does your submission pass tests?

Changes to Core Features:

  • Have you written new tests for your core changes, as applicable?
  • Have you successfully run tests with your changes locally?

pkg/fiddle has unit tests against a fake Fiddle server, and pkg/commands/vcl has CLI scenario tests for all three commands.

User Impact

You get three new commands under fastly vcl.

They also write one small file, vcl-sandbox.json, next to the CLI config file. Lose it, and the next run just starts from scratch. Nothing breaks.

Are there any considerations that need to be addressed for release?

No breaking changes. The existing vcl subcommands keep their names and behavior.

@jedisct1
jedisct1 requested a review from a team as a code owner November 7, 2026 09:14
@jedisct1 jedisct1 changed the title Fdenis/fiddle Add commands to check and run VCL code Nov 7, 2026

@philippschulte philippschulte left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for doing this! This is a substantial feature addition to the Fastly CLI. The implementation looks solid and I only found a couple of small nits we should look into before merging. Thank you!

Comment thread pkg/commands/vcl/run.go Resolved
Comment thread pkg/commands/vcl/run.go Resolved
Comment thread pkg/fiddle/stream.go Resolved
@philippschulte
philippschulte requested a balanced review from Copilot November 9, 2026 14:35

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Copilot review overview

4 open issues
What changed in this PR

Adds new Fastly CLI subcommands for compiling and executing VCL against real Fastly edge sandboxes (via Fiddle), including local proxy serving, with persisted sandbox state for faster iteration.

Changes:

  • Introduces a new pkg/fiddle client (create/update/execute + SSE result streaming) and parsing utilities for execution hostnames.
  • Adds fastly vcl check, fastly vcl run, and fastly vcl serve commands plus common helpers, sandbox state persistence, and CLI tests.
  • Wires the new vcl command group into the CLI command registry, updates token requirements, and updates changelog/output expectations.
File Description
pkg/​fiddle/​stream.go Implements SSE result stream parsing + retrying execution runner.
pkg/​fiddle/​host.go Adds exec host parsing and stable hostname generation for cache buckets.
pkg/​fiddle/​fiddle.go Adds Fiddle API client (create/update/execute) and shared helpers.
pkg/​fiddle/​fiddle_test.go Unit tests for Fiddle client, exec host parsing, and error handling.
pkg/​commands/​vcl/​common.go Shared flag parsing, VCL file loading, diagnostics formatting, sandbox state persistence.
pkg/​commands/​vcl/​check.go Adds vcl check command implementation.
pkg/​commands/​vcl/​run.go Adds vcl run command implementation + output rendering and JSON output models.
pkg/​commands/​vcl/​serve.go Adds vcl serve local reverse-proxy server with watch + resync behavior.
pkg/​commands/​vcl/​vcl_test.go CLI scenario tests for vcl check/run/serve using a fake sandbox server.
pkg/​commands/​commands.go Registers new vcl root + subcommands and hooks existing alias VCL commands beneath it.
pkg/​commands/​alias/​vcl/​root.go Updates the vcl command description text (and removes Hidden on this root).
pkg/​app/​run.go Marks vcl check/run/serve as not requiring an API token.
pkg/​app/​run_test.go Updates command listing expectations to include vcl.
CHANGELOG.md Documents the new vcl commands feature.

Review effort: Lite

Comment thread pkg/fiddle/stream.go Resolved
Comment thread pkg/commands/vcl/serve.go Resolved
Comment thread pkg/commands/vcl/serve.go Resolved
Comment thread pkg/fiddle/stream.go Resolved
The vcl command is about to grow subcommands of its own that are not
aliases of anything, so move the root command in a dedicated
pkg/commands/vcl package.
The new subcommand sends subroutine bodies to Fiddle, and reports
the diagnostics as compiler-style, editor-clickable lines (or as
JSON with --json).

Files can be passed either:
- positionally, in which case the subroutine is inferred from the
file name (recv.vcl, vcl_recv.vcl)
- or through per-subroutine flags like --recv.

The sandbox created on first use is remembered in a small state file
next to the CLI config, so later invocations update it in place
rather than creating a new one each time.
POST to the execute endpoint to get a session ID, then follow
the SSE stream.
"vcl run" publishes the VCL to the sandbox, replays a request and
reports what the client saw: summary with the status, content type,
body size and cache disposition, followed by the body.

With --verbose it also prints the full response head, the VCL
subroutine trace with return values, and any origin fetches.

And we also support --json.

To make things easy to remember, the flags are similar to the ones
from curl (--path, --method, --header, --body), plus a few specific
ones: --client-from, --connection, --shield,--no-cluster and
--follow-redirects.
Fiddle returns a hostname.

Requests sent straight to such a host run on fiddleand return the
complete response, exactly like requests declared inside the fiddle.

This is nice because we can use it as a regular HTTP backend.
The new subcommand deploys a Fiddle sandbox and starts a reverse
proxy on localhost (127.0.0.1:7676 by default) that forwards
every request to fiddle.

So that browsers can be used to directly use the sandboxed VCL service.

Serve owns a dedicated sandbox instead of sharing the one check and
run reuse, because a long-lived proxy must not have its VCL swapped
out under it by an unrelated command.

The proxy recognizes the tell-tale 982 status.

And for convenience, with --watch, the source files are monitored and the
VCL is redeployed on change.
Looks like Fiddle only follows one redirect.

So, if that lands on another redirect, show the second one
instead of a random pick.
We stopped at the first finished request, which could be the
redirect itself.

So wait until everything Fiddle started is done, and retry if a
session ends too early.
When the VCL didn't compile, we printed plaintext diagnostics
even with --json

Spotted by Philipp, thanks!
@jedisct1

Copy link
Copy Markdown
Contributor Author

Hi @philippschulte ,

And thanks again for having reviewed this. The changes you requested have been made.

@philippschulte philippschulte left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the updates and for improving the redirect handling! However, I still believe that --follow-redirects is still not working as expected. Other than that this looks great!

Comment thread pkg/commands/vcl/run.go Resolved
After 301 -> 302 -> 200, both the 302 and the 200 are marked as
redirected, and we could end up showing the 302.

Now any response that isn't a redirect is used.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants