RepoPilotOpen in app →

traefik/yaegi

Yaegi is Another Elegant Go Interpreter

Healthy

Healthy across all four use cases

weakest axis
Use as dependencyHealthy

Permissive license, no critical CVEs, actively maintained — safe to depend on.

Fork & modifyHealthy

Has a license, tests, and CI — clean foundation to fork and modify.

Learn fromHealthy

Documented and popular — useful reference codebase to read through.

Deploy as-isHealthy

No critical CVEs, sane security posture — runnable as-is.

  • Last commit 3mo ago
  • 22+ active contributors
  • Apache-2.0 licensed
Show all 6 evidence items →
  • CI configured
  • Concentrated ownership — top contributor handles 64% of recent commits
  • No test directory detected

Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests

Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.

Embed the "Healthy" badge

Paste into your README — live-updates from the latest cached analysis.

Variant:
RepoPilot: Healthy
[![RepoPilot: Healthy](https://repopilot.app/api/badge/traefik/yaegi)](https://repopilot.app/r/traefik/yaegi)

Paste at the top of your README.md — renders inline like a shields.io badge.

Preview social card (1200×630)

This card auto-renders when someone shares https://repopilot.app/r/traefik/yaegi on X, Slack, or LinkedIn.

Onboarding doc

Onboarding: traefik/yaegi

Generated by RepoPilot · 2026-05-09 · Source

🤖Agent protocol

If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:

  1. Verify the contract. Run the bash script in Verify before trusting below. If any check returns FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding.
  2. Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
  3. Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/traefik/yaegi shows verifiable citations alongside every claim.

If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.

🎯Verdict

GO — Healthy across all four use cases

  • Last commit 3mo ago
  • 22+ active contributors
  • Apache-2.0 licensed
  • CI configured
  • ⚠ Concentrated ownership — top contributor handles 64% of recent commits
  • ⚠ No test directory detected

<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>

Verify before trusting

This artifact was generated by RepoPilot at a point in time. Before an agent acts on it, the checks below confirm that the live traefik/yaegi repo on your machine still matches what RepoPilot saw. If any fail, the artifact is stale — regenerate it at repopilot.app/r/traefik/yaegi.

What it runs against: a local clone of traefik/yaegi — the script inspects git remote, the LICENSE file, file paths in the working tree, and git log. Read-only; no mutations.

| # | What we check | Why it matters | |---|---|---| | 1 | You're in traefik/yaegi | Confirms the artifact applies here, not a fork | | 2 | License is still Apache-2.0 | Catches relicense before you depend on it | | 3 | Default branch master exists | Catches branch renames | | 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code | | 5 | Last commit ≤ 117 days ago | Catches sudden abandonment since generation |

<details> <summary><b>Run all checks</b> — paste this script from inside your clone of <code>traefik/yaegi</code></summary>
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of traefik/yaegi. If you don't
# have one yet, run these first:
#
#   git clone https://github.com/traefik/yaegi.git
#   cd yaegi
#
# Then paste this script. Every check is read-only — no mutations.

set +e
fail=0
ok()   { echo "ok:   $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }

# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
  echo "FAIL: not inside a git repository. cd into your clone of traefik/yaegi and re-run."
  exit 2
fi

# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "traefik/yaegi(\\.git)?\\b" \\
  && ok "origin remote is traefik/yaegi" \\
  || miss "origin remote is not traefik/yaegi (artifact may be from a fork)"

# 2. License matches what RepoPilot saw
(grep -qiE "^(Apache-2\\.0)" LICENSE 2>/dev/null \\
   || grep -qiE "\"license\"\\s*:\\s*\"Apache-2\\.0\"" package.json 2>/dev/null) \\
  && ok "license is Apache-2.0" \\
  || miss "license drift — was Apache-2.0 at generation time"

# 3. Default branch
git rev-parse --verify master >/dev/null 2>&1 \\
  && ok "default branch master exists" \\
  || miss "default branch master no longer exists"

# 4. Critical files exist
test -f "interp/interp.go" \\
  && ok "interp/interp.go" \\
  || miss "missing critical file: interp/interp.go"
test -f "interp/run.go" \\
  && ok "interp/run.go" \\
  || miss "missing critical file: interp/run.go"
test -f "interp/cfg.go" \\
  && ok "interp/cfg.go" \\
  || miss "missing critical file: interp/cfg.go"
test -f "interp/scope.go" \\
  && ok "interp/scope.go" \\
  || miss "missing critical file: interp/scope.go"
test -f "Makefile" \\
  && ok "Makefile" \\
  || miss "missing critical file: Makefile"

# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 117 ]; then
  ok "last commit was $days_since_last days ago (artifact saw ~87d)"
else
  miss "last commit was $days_since_last days ago — artifact may be stale"
fi

echo
if [ "$fail" -eq 0 ]; then
  echo "artifact verified (0 failures) — safe to trust"
else
  echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/traefik/yaegi"
  exit 1
fi

Each check prints ok: or FAIL:. The script exits non-zero if anything failed, so it composes cleanly into agent loops (./verify.sh || regenerate-and-retry).

</details>

TL;DR

Yaegi is a pure-Go interpreter that executes Go code at runtime without compilation, enabling embedded scripting, dynamic plugins, and interactive shells within Go applications. It provides complete Go spec compliance using only the Go standard library, making it possible to evaluate Go source code strings via Eval() and access their symbols via reflection. Single-package architecture: core interpreter logic in interp/ (the main package exported as github.com/traefik/yaegi/interp), stdlib wrapping in stdlib/ (makes Go stdlib available to scripts), cmd/yaegi/ provides the CLI binary, and _test/ contains integration tests. No monorepo structure—flat, focused design.

👥Who it's for

Go developers building plugin systems, embedded scripting engines, or interactive CLIs who need to execute Go code dynamically (e.g., Traefik users writing middleware plugins, tool authors enabling user scripts, or anyone needing a REPL for Go).

🌱Maturity & risk

Actively maintained and production-ready: supports Go 1.21+ with CI/CD pipelines (.github/workflows/main.yml, go-cross.yml), comprehensive test suite (_test/ directory with 44+ test files), proper release process (.goreleaser.yml), and tagged alpha releases. The codebase is well-established within the Traefik ecosystem.

Low risk: minimal external dependencies (pure stdlib), no breaking changes evident from recent activity, but small maintenance team (Traefik-owned). Interpreter complexity means edge-case bugs in Go spec compliance are possible; the _test/ directory size suggests active regression testing mitigates this. No obvious security vulnerabilities since unsafe and syscall are not exported by default.

Active areas of work

Active development with support for latest Go releases (1.21, 1.22 mentioned in README). CI pipelines validate cross-platform builds and test compatibility. The .golangci.yml config indicates ongoing code quality enforcement. No specific milestone data visible, but regular maintenance is evident from workflow setup.

🚀Get running

Clone and build: git clone https://github.com/traefik/yaegi.git && cd yaegi && make (Makefile present). Or install the CLI directly: go install github.com/traefik/yaegi/cmd/yaegi@latest. Then run yaegi for an interactive shell, or yaegi script.go to execute a script file.

Daily commands: Interactive REPL: yaegi (post-installation). Programmatic use in Go: i := interp.New(interp.Options{}) then i.Eval("code string"). Run tests: make test (inferred from standard Go project layout). Cross-platform CI validates go-cross.yml.

🗺️Map of the codebase

  • interp/interp.go — Core interpreter entry point implementing New(), Eval(), and Use() API—every contributor must understand the main interpreter loop
  • interp/run.go — Execution engine that interprets AST nodes and manages the call stack—essential for understanding how code is actually evaluated
  • interp/cfg.go — Control flow graph builder that compiles Go code to executable instructions—central to the compilation phase
  • interp/scope.go — Symbol table and scope management for variables, functions, and types—required to understand name resolution
  • Makefile — Build configuration and test runner—defines how to build and test the interpreter
  • README.md — Project overview, feature list, and API documentation—everyone should read this first

🧩Components & responsibilities

  • interp.go (Interpreter Facade) (Go standard library (go/parser, go/ast, reflect)) — Main entry point; manages interpreter lifecycle; coordinates parsing, compilation, execution
    • Failure mode: Parse errors, compile errors → returned as error; panics on internal bugs
  • cfg.go (Compiler/CFG Builder) (go/ast, reflect for type inspection) — Converts AST to control flow graph; performs semantic analysis and type checking
    • Failure mode: Type mismatches, undefined symbols → compile-time errors; invalid CFG structure causes runtime panic
  • scope.go (Symbol Table & Values) (reflect.Value for runtime value representation) — Manages variable scopes, symbol resolution, value storage; implements scope chain
    • Failure mode: Undefined variable access → runtime error; scope pollution if not properly isolated
  • run.go (Execution Engine) (reflect, go/ast for instruction details) — Interpreter main loop; dispatches CFG instructions; manages call stack and control flow
    • Failure mode: Invalid instruction → panic; stack overflow on infinite recursion; panic on unsafe operations
  • stdlib bindings (Standard Library Integration) (reflect for dynamic function invocation) — Provides access to Go stdlib packages from interpreted code via reflection
    • Failure mode: Missing stdlib binding → undefined package error; incorrect binding signature → runtime panic

🔀Data flow

  • User code (string)interp.Eval() — Go source code passed as string to interpreter
  • interp.Eval()go/parser — Source code parsed into Go AST
  • go/parser ASTcfg.go (Compiler) — AST analyzed and converted to control flow graph
  • cfg.go CFGscope.go (Symbol Table) — Type info and symbol definitions registered during compilation
  • cfg.go CFGrun.go (Executor) — Executable CFG instructions dispatched to interpreter loop
  • run.go (during execution)scope.go (Values) — Variable lookups, assignments, and scope frame management

🛠️How to make changes

Add support for a new Go standard library package

  1. Create a new binding file in the stdlib/ directory (e.g., stdlib/math.go) that wraps the target package using reflect tags (stdlib/math.go)
  2. Use the @synthStdlib build tool to generate symbol maps from your binding file (Makefile)
  3. Register the package in interp/interp.go's init() or Use() method to make it available to interpreted code (interp/interp.go)
  4. Add test cases in _test/ directory verifying your package works correctly (e.g., _test/math0.go) (_test/math0.go)

Add a new language feature or operator

  1. Update the AST parsing logic and semantic analysis in interp/cfg.go to recognize new syntax (interp/cfg.go)
  2. Add execution rules in interp/run.go's instruction dispatcher to handle the new operation (interp/run.go)
  3. Add comprehensive test cases in _test/ with the feature name (e.g., _test/newfeature0.go, _test/newfeature1.go) (_test/newfeature0.go)
  4. Run full test suite via Makefile to ensure no regressions (Makefile)

Debug an interpreter evaluation issue

  1. Create a minimal test case in _test/ directory reproducing the issue (_test/debug0.go)
  2. Trace through interp/interp.go's Eval() method to check parsing and compilation (interp/interp.go)
  3. Inspect scope and symbol resolution in interp/scope.go for name binding errors (interp/scope.go)
  4. Debug execution in interp/run.go's interpreter loop to trace runtime behavior (interp/run.go)

🔧Why these technologies

  • Pure Go + stdlib only — Enables interpreter to run everywhere Go runs; no C dependencies; simplifies cross-platform distribution
  • Control Flow Graph (CFG) intermediate representation — Bridges gap between Go AST and execution; allows optimization and type checking; supports complex control flow
  • Reflection-based value representation — Allows seamless integration with compiled Go code; enables stdlib bindings without code generation
  • Go's native parser (go/parser) — Ensures 100% Go spec compliance; reuses Go's official AST; reduces maintenance burden

⚖️Trade-offs already made

  • Interpretation rather than compilation to bytecode

    • Why: Simpler implementation; faster startup; easier debugging
    • Consequence: Slower execution compared to compiled Go; higher per-operation overhead
  • Reflection-based stdlib bindings instead of code generation

    • Why: Runtime flexibility; no build step for stdlib; easier to extend
    • Consequence: Slightly slower stdlib calls; less type safety at compile time
  • Complete Go spec support including unsafe and syscall restrictions

    • Why: Security guarantee for embedded scripts; prevents malicious code from breaking host
    • Consequence: Cannot run all existing Go code; some packages require workarounds
  • Scope-based symbol table (nested scopes with parent pointers)

    • Why: Natural fit for Go's scoping rules; efficient lookup
    • Consequence: More complex scope management; potential memory overhead for deeply nested scopes

🚫Non-goals (don't propose these)

  • Real-time execution performance competitive with compiled Go
  • JIT compilation or optimization beyond basic CFG construction
  • Support for CGo or direct C integration
  • Execution of arbitrary unsafe code or syscall operations
  • Complete debugger/IDE integration (though stepping is possible)

🪤Traps & gotchas

No required environment variables or external services. Watch for: (1) the interp package uses reflect heavily—understanding Go reflection is non-negotiable, (2) only Go 1.21+ officially supported; older versions may have untested edge cases, (3) _test files use specific naming (a1.go, a2.go, etc.) for ordering—adding tests requires careful naming, (4) unsafe and syscall deliberately not exported; attempting to use them in scripts will fail silently or with cryptic errors.

🏗️Architecture

💡Concepts to learn

  • AST Walking / Tree Traversal — Yaegi interprets Go by walking the Abstract Syntax Tree (parsed via go/ast); understanding how it recursively evaluates nodes is essential to grasping the interp/ package
  • Go Reflection (reflect package) — The interpreter uses reflection extensively to call functions, access struct fields, and convert between reflect.Value and concrete types at runtime
  • Symbol Tables / Scope Management — Yaegi maintains interpreter scopes for variables, functions, and imports; understanding how symbols are bound and resolved is critical for debugging script execution
  • Go Type System & Type Assertions — The interpreter must validate type compatibility and perform runtime type assertions when scripts call compiled Go functions; this is central to the interp/run.go logic
  • Bytecode Interpretation vs. JIT Compilation — Yaegi uses direct AST interpretation (no bytecode generation or JIT); understanding this choice clarifies why startup is fast but execution is slower than compiled Go
  • Package Import System & Symbol Binding — Scripts import packages via stdlib.Symbols; grasping how packages are reflected and bound into script scope is essential for adding new stdlib support
  • Defer, Panic, and Recovery — The interpreter must faithfully implement Go's defer stack and panic/recover semantics; this is non-trivial in an interpreter context and tested extensively in _test/
  • golang/go — The canonical Go compiler and runtime; Yaegi's specification compliance and AST node handling directly reference Go's own implementation
  • traefik/traefik — Yaegi's primary consumer; Traefik uses it for dynamic middleware and plugin evaluation, demonstrating real-world embedded interpreter use
  • go-lua/lua — Alternative embedded scripting language for Go; useful for comparative study if you need lightweight scripting vs. full Go compatibility
  • sbinet/go-python — Python interpreter bindings for Go; illustrates alternate approaches to embedding dynamic language runtimes in Go applications
  • hashicorp/hcl — Configuration language for Go; demonstrates DSL design patterns relevant to understanding why Yaegi chose full Go syntax over a simpler config format

🪄PR ideas

To work on one of these in Claude Code or Cursor, paste: Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.

Add comprehensive test coverage for _test directory with automated validation

The _test directory contains 80+ Go test files (a1.go through a44.go, add0-2.go, addr0-5.go, and, append0-4.go, etc.) but there's no evidence of automated test execution or validation. These appear to be test cases for the interpreter itself. Creating a test harness that systematically validates all these files and integrating it into the CI pipeline (main.yml) would ensure interpreter correctness and catch regressions early.

  • [ ] Create a test runner script in Makefile or as a Go test file that discovers and executes all _test/*.go files
  • [ ] Add validation logic to ensure each test file behaves as expected (parsing, execution, error handling)
  • [ ] Integrate the test runner into .github/workflows/main.yml to run on every commit
  • [ ] Document the test structure in CONTRIBUTING.md with examples of how to add new interpreter test cases

Add cross-platform interpreter CLI tool with interactive REPL mode

The repo has a go-cross.yml workflow for cross-platform builds, but there's no visible CLI entry point or REPL implementation listed in the file structure. A proper interactive shell/REPL mode would make Yaegi more accessible to users and provide a better developer experience. This would involve creating a cmd/yaegi/main.go application with both batch script execution and interactive modes.

  • [ ] Create cmd/yaegi/main.go with CLI argument parsing for script files and interactive mode
  • [ ] Implement interactive REPL with history, line editing, and multiline support
  • [ ] Add tests in cmd/yaegi/main_test.go for CLI behavior
  • [ ] Update README.md with CLI usage examples and update CONTRIBUTING.md with CLI contribution guidelines

Add benchmark tests and performance profiling suite

As a Go interpreter, performance is critical for adoption. The _test directory has many test cases but no evidence of benchmark tests to track interpreter performance. Creating a benchmarking suite would help identify bottlenecks and prevent performance regressions. This is especially important given the complexity of implementing Go's specification.

  • [ ] Create benchmarks/ directory with *_bench_test.go files testing interpreter performance (parsing, execution, reflection operations)
  • [ ] Add benchmark execution to the main.yml CI workflow to track performance over time
  • [ ] Create a benchstat comparison script to detect performance regressions in PRs
  • [ ] Document benchmark methodology and results in a BENCHMARKS.md file

🌿Good first issues

  • Add docstring comments to exported functions in interp/interp.go and interp/eval.go; currently the API surface lacks GoDoc coverage, making it harder for users to understand Options and return types.
  • Expand _test/ suite to cover more Go spec edge cases (e.g., a45.go–a50.go testing generics, defer chains, and panic recovery); the 44-file test suite leaves gaps in coverage.
  • Document the stdlib/ bindings process: add a STDLIB_BINDING_GUIDE.md explaining how to add new stdlib package support or extend existing ones (e.g., reflecting go/ast, go/parser).

Top contributors

Click to expand

📝Recent commits

Click to expand
  • fcb76d1 — Fix nil pointer dereference when sending to binary channel type alias (pk910)
  • bdb9884 — chore: upgrade golangci-lint to 2.4.0 (mvertes)
  • 98d8720 — chore: pin GitHub Actions to SHA hashes for supply chain security (darkweaver87)
  • d93266d — fix: call fmt.Errorf with wrong error (alingse)
  • 938db1c — fix: use path instead of filepath (secDre4mer)
  • f616581 — fix: correctly resolve fields of aliased structs (#1679) (secDre4mer)
  • 1e3e91d — feat: ensure that untyped values are converted to the same type (#1677) (secDre4mer)
  • 14d3137 — chore: bump golangci-lint and fix config (#1680) (mvertes)
  • e686f55 — interp: fix a missing implicit type conversion for binary expression (mvertes)
  • 9c4dcfc — automatic loop variables in for loops ("loop var"), consistent with go 1.22 behavior (rcoreilly)

🔒Security observations

Yaegi demonstrates a security-conscious design with built-in restrictions on unsafe packages by default. The codebase appears to be a pure Go interpreter with no external dependencies visible, reducing supply chain risks. However, the security posture is somewhat unclear due to incomplete documentation and a large, opaquely-named test suite. The primary concern is ensuring that unsafe operations remain permanently blocked and cannot be circumvented through API usage or configuration. The project would benefit from more explicit security documentation and clearer test file naming conventions to facilitate security audits.

  • Medium · Unsafe Package Exported by Default — README.md, Core interpreter API design. The README states that 'unsafe' and 'syscall' packages are 'neither used nor exported by default', but this suggests they could be exported conditionally. If the interpreter allows loading of unsafe packages through configuration or API misuse, it could enable arbitrary memory access and system calls within the interpreter context. Fix: Ensure unsafe and syscall packages are permanently blocked from being loaded in the interpreter, regardless of configuration or API usage. Implement whitelist-based package loading for security-sensitive environments.
  • Low · Test Files Lack Clear Purpose Documentation — _test/ directory. The _test directory contains 100+ test files (a1.go through a44.go, add0.go through add4.go, etc.) with cryptic naming conventions. Without clear documentation of what these tests cover or their security implications, it's difficult to assess if all security-relevant code paths are tested. Fix: Add comprehensive documentation or comments in test files explaining their purpose. Use more descriptive test file names (e.g., test_unsafe_operations.go instead of a1.go) to improve code clarity and security review capability.
  • Low · Incomplete README Security Section — README.md. The README security note about 'unsafe' and 'syscall' packages is incomplete (text cuts off at 'Sup'). This suggests potential documentation gaps regarding security features and guarantees of the interpreter. Fix: Complete the README security documentation with clear statements about: (1) security boundaries and guarantees, (2) threat model, (3) safe vs unsafe operations, and (4) any known security limitations.

LLM-derived; treat as a starting point, not a security audit.


Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.

Healthy signals · traefik/yaegi — RepoPilot