Compiled Targets
How generated scripts are cached, invalidated, and committed for free CI runs.
The compile-once model
Running the LLM on every vibe run would be expensive and slow. Vibefile uses target compilation: the first time a codegen target runs, the generated shell is saved to disk. Subsequent runs execute the saved shell directly — no LLM call, no latency, no API cost.
Mental model: the first vibe run build is the compile step. Every run after that executes the compiled output. The Vibefile is source code. The generated shell script is the binary.
first run subsequent runs
────────────────────────────── ───────────────────────────
vibe run build vibe run build
→ collect repo context → load .vibe/compiled/build.sh
→ call LLM API (cost) → execute script (free)
→ save .vibe/compiled/build.sh
→ execute script
Cache location
Compiled scripts live in .vibe/compiled/. This directory must be committed to version control.
Why commit:
- CI runs are free and fast. CI uses the cached script — no LLM, no API key, no latency.
- One person pays the cost. Dev runs
vibe run(or--recompile), the LLM generates the script, it’s committed with the Vibefile change. All subsequent runs are free. - Auditability. Code review shows both intent change (recipe) and implementation change (compiled shell).
- Reproducibility. The same script runs everywhere. No variance from different LLM responses.
my-project/
├── Vibefile
└── .vibe/
├── config.yaml
├── compiled/
│ ├── build.sh
│ ├── build.lock
│ ├── test.sh
│ └── test.lock
└── skills/
Do not add .vibe/compiled/ to .gitignore. The vibe init command generates an appropriate .gitignore that keeps compiled output tracked.
Cache invalidation
A target’s compiled output is invalidated when its inputs change. The .lock file stores a checksum of everything sent to the LLM:
# .vibe/compiled/build.lock
recipe: "compile and bundle the project for production"
model: claude-sonnet-4-6
context_files:
package.json: sha256:a1b2c3...
tsconfig.json: sha256:d4e5f6...
Vibefile: sha256:7g8h9i...
variables:
env: production
generated_at: 2025-03-08T14:22:00Z
On each run, the CLI recomputes the checksum and compares it to the .lock file. If anything changed — recipe, variable, config file, model — the target recompiles. Otherwise the cached script runs directly. This mirrors how Turborepo and Bazel handle input hashing, applied to LLM-generated code.
What triggers a recompile
| Change | Recompiles? |
|---|---|
| Recipe string edited | Yes |
| Variable value changed | Yes |
Relevant context file changed (e.g. package.json) |
Yes |
| Model version changed | Yes |
| Skill updated | Yes |
| Unrelated source files changed | No |
| Running on a different machine (same inputs) | No |
Codegen targets only
Compilation applies to codegen mode only. Agent targets (@mcp) are inherently dynamic — they interact with live services, inspect state, and adapt at runtime. Caching their output would be meaningless and potentially dangerous.
build:
"compile and bundle for production"
# compiled — same commands every time
deploy: build
"deploy to production on fly.io and verify health"
@mcp fly-mcp
# not compiled — agent adapts to live state each run
Force recompile
vibe run build --recompile # force LLM call for this target
vibe run build --recompile-all # force LLM call for this target and all deps
Reviewing compiled output
Because compiled scripts are committed, they’re auditable. Code review shows both intent (recipe) and implementation (shell). Teams catch problems before production.
Hand-editing compiled scripts is supported: the CLI detects manual modification (checksum mismatch without recipe change) and warns but still executes. Use --recompile to regenerate from the recipe.