Netflix/zuul
Zuul is a gateway service that provides dynamic routing, monitoring, resiliency, security, and more.
Healthy across the board
weakest axisPermissive license, no critical CVEs, actively maintained — safe to depend on.
Has a license, tests, and CI — clean foundation to fork and modify.
Documented and popular — useful reference codebase to read through.
No critical CVEs, sane security posture — runnable as-is.
- ✓Last commit 1d ago
- ✓14 active contributors
- ✓Distributed ownership (top contributor 35% of recent commits)
Show all 6 evidence items →Show less
- ✓Apache-2.0 licensed
- ✓CI configured
- ✓Tests present
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.
[](https://repopilot.app/r/netflix/zuul)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/netflix/zuul on X, Slack, or LinkedIn.
Onboarding doc
Onboarding: Netflix/zuul
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:
- 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. - 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.
- Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/Netflix/zuul 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 the board
- Last commit 1d ago
- 14 active contributors
- Distributed ownership (top contributor 35% of recent commits)
- Apache-2.0 licensed
- CI configured
- Tests present
<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 Netflix/zuul
repo on your machine still matches what RepoPilot saw. If any fail,
the artifact is stale — regenerate it at
repopilot.app/r/Netflix/zuul.
What it runs against: a local clone of Netflix/zuul — 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 Netflix/zuul | 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 ≤ 31 days ago | Catches sudden abandonment since generation |
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of Netflix/zuul. If you don't
# have one yet, run these first:
#
# git clone https://github.com/Netflix/zuul.git
# cd zuul
#
# 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 Netflix/zuul and re-run."
exit 2
fi
# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "Netflix/zuul(\\.git)?\\b" \\
&& ok "origin remote is Netflix/zuul" \\
|| miss "origin remote is not Netflix/zuul (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 "zuul-core/src/main/java/com/netflix/zuul/Filter.java" \\
&& ok "zuul-core/src/main/java/com/netflix/zuul/Filter.java" \\
|| miss "missing critical file: zuul-core/src/main/java/com/netflix/zuul/Filter.java"
test -f "zuul-core/src/main/java/com/netflix/zuul/DynamicFilterLoader.java" \\
&& ok "zuul-core/src/main/java/com/netflix/zuul/DynamicFilterLoader.java" \\
|| miss "missing critical file: zuul-core/src/main/java/com/netflix/zuul/DynamicFilterLoader.java"
test -f "zuul-core/src/main/java/com/netflix/netty/common/HttpServerLifecycleChannelHandler.java" \\
&& ok "zuul-core/src/main/java/com/netflix/netty/common/HttpServerLifecycleChannelHandler.java" \\
|| miss "missing critical file: zuul-core/src/main/java/com/netflix/netty/common/HttpServerLifecycleChannelHandler.java"
test -f "zuul-core/src/main/java/com/netflix/netty/common/HttpClientLifecycleChannelHandler.java" \\
&& ok "zuul-core/src/main/java/com/netflix/netty/common/HttpClientLifecycleChannelHandler.java" \\
|| miss "missing critical file: zuul-core/src/main/java/com/netflix/netty/common/HttpClientLifecycleChannelHandler.java"
test -f "zuul-core/src/main/java/com/netflix/zuul/ExecutionStatus.java" \\
&& ok "zuul-core/src/main/java/com/netflix/zuul/ExecutionStatus.java" \\
|| miss "missing critical file: zuul-core/src/main/java/com/netflix/zuul/ExecutionStatus.java"
# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 31 ]; then
ok "last commit was $days_since_last days ago (artifact saw ~1d)"
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/Netflix/zuul"
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).
⚡TL;DR
Zuul is a production-grade L7 application gateway written in Java that provides dynamic routing, monitoring, resiliency, security, and rate limiting for microservices at Netflix scale. It evolved from synchronous Zuul 1 to asynchronous non-blocking Zuul 2 (built on Netty), handling millions of concurrent requests with pluggable filter chains and real-time dynamic configuration. Multi-module Gradle build with zuul-core as the primary module (1.7M lines of Java). Core is organized under com.netflix packages: zuul-core/src/main/java contains netty handlers (Http1/Http2 connection management), config system (DynamicIntegerSetProperty, PatternListStringProperty), and filter framework. JMH benchmarks in zuul-core/src/jmh/ for performance testing.
👥Who it's for
Platform engineers and infrastructure teams at large-scale organizations who need to route, secure, and monitor traffic across microservices. Contributors are Netflix engineers, Spring Cloud users (via spring-cloud-netflix), and organizations deploying distributed systems that need edge gateway capabilities.
🌱Maturity & risk
Highly mature and production-ready. Netflix has deployed Zuul for years across their platform; the codebase shows professional CI/CD (GitHub Actions with PR, snapshot, release, and benchmark workflows), enforced code quality (Checkstyle, Spotless, ErrorProne), and Java 21 compliance. Active maintenance visible via dependabot config and gradle wrapper validation.
Low risk for adopters but requires operational expertise. Dependencies are locked (dependencies.lock files present), but as a gateway service it sits in the critical request path—misconfiguration affects all downstream services. Single Netflix-owned repository means release timing depends on Netflix's schedule; however, the project is open-source with clear release process (tag-driven GitHub Actions).
Active areas of work
Repository is actively maintained with dependabot monitoring, gradle wrapper validation, and snapshot releases. CI pipeline includes benchmark workflows (.github/workflows/benchmark.yml) suggesting ongoing performance optimization. Build uses Java 21 (toolchain configured in build.gradle).
🚀Get running
git clone https://github.com/Netflix/zuul.git
cd zuul
./gradlew build
./gradlew :zuul-core:test
Uses Gradle 7+ wrapper; no external services required for local build.
Daily commands:
This is a library/gateway, not an executable app. Build with ./gradlew build. To run benchmarks: ./gradlew :zuul-core:jmh. To integrate into a service, add zuul-core as a dependency and configure filters. See Netflix blog posts and wiki for deployment patterns.
🗺️Map of the codebase
zuul-core/src/main/java/com/netflix/zuul/Filter.java— Core abstraction for all filter implementations; foundational to Zuul's filter chain execution modelzuul-core/src/main/java/com/netflix/zuul/DynamicFilterLoader.java— Loads and manages dynamic filter lifecycle; critical for runtime filter composition and hot-reloadingzuul-core/src/main/java/com/netflix/netty/common/HttpServerLifecycleChannelHandler.java— Main Netty channel handler for inbound HTTP lifecycle; entry point for request processing in Zuul gatewayzuul-core/src/main/java/com/netflix/netty/common/HttpClientLifecycleChannelHandler.java— Manages outbound client connections to origin servers; responsible for response aggregation and forwardingzuul-core/src/main/java/com/netflix/zuul/ExecutionStatus.java— Enum defining filter execution states and outcomes; governs control flow through request/response pipelineszuul-core/src/main/java/com/netflix/netty/common/metrics/HttpMetricsChannelHandler.java— Instruments request/response metrics collection; essential for monitoring and observability integrationbuild.gradle— Root build configuration with Netflix OSS settings, Netty, and JMH dependencies; defines project structure
🛠️How to make changes
Add a Custom Request/Response Filter
- Create a new class extending com.netflix.zuul.Filter and implement shouldFilter() (routing logic) and run() (filter logic) (
zuul-core/src/main/java/com/netflix/zuul/Filter.java) - Set filterType() to one of PRE, ROUTE, POST, or ERROR using FilterCategory enum (
zuul-core/src/main/java/com/netflix/zuul/FilterCategory.java) - Return ExecutionStatus.SUCCESS or SKIP_NEXT from run() to control pipeline flow (
zuul-core/src/main/java/com/netflix/zuul/ExecutionStatus.java) - Register filter with DynamicFilterLoader by placing class in configured filter directory for dynamic loading or implement FilterFactory for static registration (
zuul-core/src/main/java/com/netflix/zuul/DynamicFilterLoader.java)
Instrument Custom Metrics for a New Feature
- Create a Netty ChannelInboundHandler or ChannelOutboundHandler to capture the event of interest (
zuul-core/src/main/java/com/netflix/netty/common/metrics/HttpMetricsChannelHandler.java) - Use EventLoopGroupMetrics or EventLoopMetrics to register gauge/counter metrics keyed by feature name (
zuul-core/src/main/java/com/netflix/netty/common/metrics/EventLoopGroupMetrics.java) - Add handler to the Netty pipeline in HttpServerLifecycleChannelHandler or HttpClientLifecycleChannelHandler constructor (
zuul-core/src/main/java/com/netflix/netty/common/HttpServerLifecycleChannelHandler.java)
Add Request Throttling or Connection Limiting Logic
- Extend or wrap MaxInboundConnectionsHandler to enforce custom connection/request count limits (
zuul-core/src/main/java/com/netflix/netty/common/throttle/MaxInboundConnectionsHandler.java) - Emit RequestRejectedEvent when a request or connection is rejected, passing the RejectionType (GLOBAL_LIMIT, SERVICE_LIMIT, etc.) (
zuul-core/src/main/java/com/netflix/netty/common/throttle/RequestRejectedEvent.java) - Register the custom throttle handler early in HttpServerLifecycleChannelHandler's pipeline initialization (
zuul-core/src/main/java/com/netflix/netty/common/HttpServerLifecycleChannelHandler.java)
Add Support for a New Proxy Protocol or Header Validation
- Create a new ChannelInboundHandler in zuul-core/src/main/java/com/netflix/netty/common/proxyprotocol/ to parse the protocol (
zuul-core/src/main/java/com/netflix/netty/common/proxyprotocol/HAProxyMessageChannelHandler.java) - Extract and validate source address, storing it in channel attributes for downstream filter/handler access (
zuul-core/src/main/java/com/netflix/netty/common/SourceAddressChannelHandler.java) - Optionally sanitize untrusted headers using StripUntrustedProxyHeadersHandler as a reference (
zuul-core/src/main/java/com/netflix/netty/common/proxyprot)
🪤Traps & gotchas
No external services required locally, but: Spotless (code formatter) is enforced on CI—run ./gradlew spotlessApply before committing to avoid CI failures. ErrorProne static analysis is enabled; some legitimate patterns may trigger warnings. Java 21 is required (toolchain validation happens in build). Gradle wrapper validation is strict (.github/workflows/gradle-wrapper-validation.yml); don't update wrapper without following Netflix OSS guidelines. Connection handler tests may be sensitive to timing due to Netty async nature.
💡Concepts to learn
- Non-blocking async I/O with Netty — Zuul 2's entire performance model depends on Netty's event-driven architecture; understanding ChannelHandlers, ByteBufs, and event loops is essential to modifying routing logic or connection management
- HTTP/2 Connection Management — Zuul handles both HTTP/1.1 and HTTP/2 with separate handler classes (Http1ConnectionExpiryHandler vs Http2ConnectionExpiryHandler); understanding multiplexing, stream limits, and flow control is needed for performance tuning
- Filter Chain Pattern — Zuul's core extension mechanism is a pluggable filter chain (similar to servlet filters but async); understanding request/response interception, ordering, and error handling is fundamental to customization
- Dynamic Configuration without Restart — DynamicIntegerSetProperty and PatternListStringProperty allow routing rules to change at runtime; understanding property evaluation and listener patterns prevents stale config bugs
- Connection Pooling and Expiry — CloseOnIdleStateHandler, Http1ConnectionExpiryHandler, and Http2ConnectionExpiryHandler manage connection lifecycle; misconfiguration causes resource leaks or premature closures in production
- Thread Factory and Thread Naming — CategorizedThreadFactory ensures observability in production by naming Netty event loop threads; important for debugging thread contention and monitoring via profilers
- ByteBuf Memory Management — ByteBufUtil handles pooling and zero-copy patterns critical to Zuul's latency profile; leaks here cause GC pressure and OutOfMemory errors at scale
🔗Related repos
spring-cloud/spring-cloud-netflix— Official Spring Cloud integration for Zuul, used by boot developers to embed Zuul 1/2 as a gateway serviceNetflix/hystrix— Complementary Netflix OSS project for circuit breaking and fault tolerance, often deployed alongside Zuul for resilienceNetflix/archaius— Netflix's dynamic configuration library that Zuul's config system (DynamicIntegerSetProperty, etc.) builds uponenvoyproxy/envoy— Modern L7 proxy alternative to Zuul 2, built in C++ for lower latency; both solve gateway routing at scaleKong/kong— Open-source API gateway with plugin architecture, positioned as an alternative to Zuul for organizations not committed to Netflix stack
🪄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 unit tests for HTTP/2 frame handling in DynamicHttp2FrameLogger
The zuul-core/src/main/java/com/netflix/netty/common/http2/DynamicHttp2FrameLogger.java file exists but there's no corresponding test file visible in the structure. Given Zuul's critical role as a gateway handling HTTP/2 traffic, comprehensive tests for frame logging are essential to catch regressions in protocol handling. This would improve reliability and serve as documentation for expected behavior.
- [ ] Create zuul-core/src/test/java/com/netflix/netty/common/http2/DynamicHttp2FrameLoggerTest.java
- [ ] Add test cases for various HTTP/2 frame types (DATA, HEADERS, SETTINGS, GOAWAY, etc.)
- [ ] Test dynamic logging level switching without dropping frames
- [ ] Add tests for edge cases like oversized frames and frame ordering
- [ ] Run with existing test suite and verify coverage metrics
Add integration tests for HeadersBenchmark performance regressions in CI
The zuul-core/src/jmh/java/com/netflix/zuul/message/HeadersBenchmark.java exists but there's no visible JMH benchmark workflow in .github/workflows/. Headers are a critical path in gateway operations, and header parsing performance directly impacts throughput. Adding a benchmark CI workflow would catch performance regressions before release and provide historical performance tracking.
- [ ] Create .github/workflows/jmh-benchmark.yml workflow file
- [ ] Configure workflow to run JMH benchmarks on PR/push using gradle jmh task
- [ ] Add benchmark result storage/comparison (GitHub Actions artifact or external service)
- [ ] Document expected baseline performance in zuul-core/src/jmh/README.md
- [ ] Add benchmark CI link to README.md with instructions for contributors
Add missing unit tests for channel configuration and lifecycle handlers
Multiple critical Netty channel handlers exist (HttpServerLifecycleChannelHandler, HttpClientLifecycleChannelHandler, CloseOnIdleStateHandler, ConnectionCloseChannelAttributes, etc.) but no test files are evident in the file structure. These handlers manage connection lifecycle, timeouts, and closure—core to gateway stability. Adding comprehensive tests would ensure correct behavior across HTTP/1 and HTTP/2 scenarios.
- [ ] Create zuul-core/src/test/java/com/netflix/netty/common/ test directory structure matching source
- [ ] Add HttpServerLifecycleChannelHandlerTest covering initialization, channel events, and cleanup
- [ ] Add HttpClientLifecycleChannelHandlerTest with scenarios for connection pooling and reuse
- [ ] Add CloseOnIdleStateHandlerTest verifying idle detection and graceful closure
- [ ] Add ChannelConfig integration tests using EmbeddedChannel from Netty test utilities
- [ ] Verify tests pass with both HTTP/1 and HTTP/2 configurations
🌿Good first issues
- Add JMH benchmarks for the connection expiry handlers (Http1ConnectionExpiryHandler, Http2ConnectionExpiryHandler) in zuul-core/src/jmh/—currently only HeadersBenchmark exists. This would expose performance characteristics of idle connection cleanup.
- Create integration tests or documentation for ByteBufUtil pooling behavior (zuul-core/src/main/java/com/netflix/netty/common/ByteBufUtil.java)—currently no benchmarks or tests visible in JMH module for buffer allocation patterns under load.
- Expand DynamicIntegerSetProperty and PatternListStringProperty unit tests (zuul-core/src/main/java/com/netflix/config/)—dynamic property evaluation in a gateway is critical; add property change listener tests, edge cases, and concurrent update scenarios.
⭐Top contributors
Click to expand
Top contributors
- @gavinbunney — 35 commits
- @jguerra — 22 commits
- @dependabot[bot] — 9 commits
- @tappenzeller — 8 commits
- @lalernehl — 8 commits
📝Recent commits
Click to expand
Recent commits
515d5b3— Update netty to 4.2.13.Final (#2130) (fool1280)2eb6aac— fmt (gavinbunney)8a2ca1a— Correctly handle http conn state when origin responds beforeLastHttpContentis sent (#2129) (gavinbunney)3b98e3a— Remove decoded path in HttpRequestMessageImpl (#2115) (fool1280)e33bfe4— Ensure Transfer-Encoding is not allowed on HTTP/1.0 requests (#2125) (gavinbunney)f5d4f21— Remove unused methods from ProxyUtils (#2126) (gavinbunney)e137cc4— Add HTTP/1.1 request framing enforcer (#2124) (gavinbunney)2708d36— fmt (gavinbunney)c1d9cef— Configure Continuation default to netty default (#2114) (jguerra)0cf10cf— maxConsecutiveContinuationFrames configuration (#2112) (tappenzeller)
🔒Security observations
- Medium · Outdated Gradle Wrapper Version —
gradle/wrapper/gradle-wrapper.jar, gradle/wrapper/gradle-wrapper.properties. The gradle-wrapper.jar file is included in version control. While Gradle wrapper validation is configured in CI/CD (gradle-wrapper-validation.yml), the wrapper version should be regularly updated to receive security patches and bug fixes. The current setup may be using an older Gradle version. Fix: Regularly update the Gradle wrapper to the latest stable version using './gradlew wrapper --gradle-version=<latest>' and verify integrity with gradle-wrapper-validation workflow. - Medium · Potential Information Disclosure via Debug Logging —
zuul-core/src/main/java/com/netflix/netty/common/metrics/, zuul-core/src/main/java/com/netflix/netty/common/http2/DynamicHttp2FrameLogger.java. Multiple HTTP/HTTP2 handlers and metric classes (HttpMetricsChannelHandler, Http2MetricsChannelHandlers, DynamicHttp2FrameLogger) are present that handle sensitive request/response data. If debug or frame logging is enabled in production, this could expose sensitive headers, request bodies, or SSL/TLS information. Fix: Ensure debug logging and frame logging (DynamicHttp2FrameLogger) are disabled in production configurations. Implement log filtering for sensitive headers (Authorization, Cookie, etc.). Use environment-based configuration to control logging levels. - Medium · SSL/TLS Configuration Requires Validation —
zuul-core/src/main/java/com/netflix/netty/common/ssl/ServerSslConfig.java, zuul-core/src/main/java/com/netflix/netty/common/ssl/SslHandshakeInfo.java. ServerSslConfig and SslHandshakeInfo classes suggest SSL/TLS is configured, but without visibility into their implementation, potential issues could include: weak cipher suites, missing certificate pinning, or accepting invalid certificates. Fix: Ensure SSL/TLS configuration enforces: TLS 1.2+ only, strong cipher suites, proper certificate validation, and consider implementing certificate pinning for critical connections. Audit SslHandshakeInfo for proper validation logic. - Medium · Proxy Protocol Header Handling —
zuul-core/src/main/java/com/netflix/netty/common/proxyprotocol/StripUntrustedProxyHeadersHandler.java, zuul-core/src/main/java/com/netflix/netty/common/proxyprotocol/. The StripUntrustedProxyHeadersHandler suggests the gateway handles X-Forwarded-For and proxy protocol headers. Improper validation could allow header spoofing attacks where attackers can masquerade as different clients or bypass authentication. Fix: Ensure StripUntrustedProxyHeadersHandler only trusts proxy headers from known, trusted sources. Implement strict whitelist of allowed proxy header origins. Validate HAProxyMessageChannelHandler properly distinguishes between trusted and untrusted connections. - Low · Build Configuration Check - Dependency Lock Files —
dependencies.lock, zuul-core/dependencies.lock. The project uses dependency-lock files (dependencies.lock, zuul-core/dependencies.lock) which is good practice, but these must be regularly updated to incorporate security patches from transitive dependencies. Fix: Implement automated dependency scanning in CI/CD pipeline. Configure Dependabot (already present in .github/dependabot.yml) to check for vulnerable dependencies regularly. Lock files should be reviewed and updated at least quarterly. - Low · Resource Leak Detection in Production —
zuul-core/src/main/java/com/netflix/netty/common/metrics/InstrumentedResourceLeakDetector.java. InstrumentedResourceLeakDetector suggests custom resource leak detection is implemented. While this is good for development, excessive resource leak detection in production could impact performance and create denial-of-service opportunities. Fix: Ensure resource leak detection is disabled or minimally configured in production. Make leak detection level configurable via environment variables with strict defaults for production deployments. - Low · Connection Throttling Configuration —
undefined. MaxInboundConnectionsHandler exists for throttling, but without visibility into configuration defaults, the gateway could be vulnerable to connection exhaustion attacks if limits are too high or not properly enforced Fix: undefined
LLM-derived; treat as a starting point, not a security audit.
👉Where to read next
- Open issues — current backlog
- Recent PRs — what's actively shipping
- Source on GitHub
Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.