brianmario/mysql2
A modern, simple and very fast Mysql library for Ruby - binding to libmysql
Healthy across the board
Permissive 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 6d ago
- ✓30+ active contributors
- ✓Distributed ownership (top contributor 31% of recent commits)
Show 3 more →Show less
- ✓MIT 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/brianmario/mysql2)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/brianmario/mysql2 on X, Slack, or LinkedIn.
Onboarding doc
Onboarding: brianmario/mysql2
Generated by RepoPilot · 2026-05-10 · 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/brianmario/mysql2 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 6d ago
- 30+ active contributors
- Distributed ownership (top contributor 31% of recent commits)
- MIT 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 brianmario/mysql2
repo on your machine still matches what RepoPilot saw. If any fail,
the artifact is stale — regenerate it at
repopilot.app/r/brianmario/mysql2.
What it runs against: a local clone of brianmario/mysql2 — 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 brianmario/mysql2 | Confirms the artifact applies here, not a fork |
| 2 | License is still MIT | 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 ≤ 36 days ago | Catches sudden abandonment since generation |
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of brianmario/mysql2. If you don't
# have one yet, run these first:
#
# git clone https://github.com/brianmario/mysql2.git
# cd mysql2
#
# 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 brianmario/mysql2 and re-run."
exit 2
fi
# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "brianmario/mysql2(\\.git)?\\b" \\
&& ok "origin remote is brianmario/mysql2" \\
|| miss "origin remote is not brianmario/mysql2 (artifact may be from a fork)"
# 2. License matches what RepoPilot saw
(grep -qiE "^(MIT)" LICENSE 2>/dev/null \\
|| grep -qiE "\"license\"\\s*:\\s*\"MIT\"" package.json 2>/dev/null) \\
&& ok "license is MIT" \\
|| miss "license drift — was MIT 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 "lib/mysql2.rb" \\
&& ok "lib/mysql2.rb" \\
|| miss "missing critical file: lib/mysql2.rb"
test -f "ext/mysql2/client.c" \\
&& ok "ext/mysql2/client.c" \\
|| miss "missing critical file: ext/mysql2/client.c"
test -f "ext/mysql2/result.c" \\
&& ok "ext/mysql2/result.c" \\
|| miss "missing critical file: ext/mysql2/result.c"
test -f "lib/mysql2/client.rb" \\
&& ok "lib/mysql2/client.rb" \\
|| miss "missing critical file: lib/mysql2/client.rb"
test -f "ext/mysql2/extconf.rb" \\
&& ok "ext/mysql2/extconf.rb" \\
|| miss "missing critical file: ext/mysql2/extconf.rb"
# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 36 ]; then
ok "last commit was $days_since_last days ago (artifact saw ~6d)"
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/brianmario/mysql2"
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
mysql2 is a high-performance Ruby C extension that binds directly to libmysqlclient (or MariaDB Connector/C) for fast MySQL/MariaDB database connections. It provides three core classes—Mysql2::Client for connections, Mysql2::Result for query results (with Enumerable), and Mysql2::Statement for prepared statements—optimized for the common use case of connect-query-iterate workflows with forced UTF-8 encoding and encoding-aware MySQL API calls. Hybrid Ruby+C structure: lib/ contains pure Ruby bindings and high-level Client class; ext/mysql2/ holds C code (client.c, result.c, statement.c, infile.c) wrapping libmysqlclient; benchmark/ and examples/ demonstrate integration with ActiveRecord/Sequel and EventMachine; ci/ contains Dockerfiles and shell scripts for testing across MySQL/MariaDB versions.
👥Who it's for
Ruby developers (especially Rails developers) building production applications that need efficient MySQL database access without ORM overhead. It's used by teams deploying ActiveRecord apps, Sequel ORM users, and developers writing raw SQL queries who want C-level performance and minimal abstraction.
🌱Maturity & risk
Production-ready and actively maintained. The repo shows active CI/CD with GitHub Actions for multiple MySQL versions (5.7, 8.0, 8.4) and MariaDB variants (10.6, 10.11, 11.4), a comprehensive CHANGELOG, and a well-established RSpec test suite. Commits and releases are ongoing, indicating healthy stewardship.
Low risk overall, but single-maintainer concerns exist (brianmario is primary). The C extension ties to libmysqlclient versions and MySQL API stability; breaking changes in MySQL 8.4+ or OpenSSL upgrades occasionally require C code updates. Dependency on system-level MySQL dev packages (libmysqlclient-dev) adds installation friction on some platforms.
Active areas of work
Active maintenance focused on compatibility: recent CI workflows test against MySQL 8.0–8.4 and MariaDB 10.6–11.4; Rubocop linting is enforced; the changelog shows steady bug fixes and version support additions. The project is not pursuing major rewrites, but rather stability and incremental improvements.
🚀Get running
git clone https://github.com/brianmario/mysql2.git
cd mysql2
bundle install
# Install system MySQL dev libraries (e.g., libmysqlclient-dev on Ubuntu)
bundle exec rake compile
bundle exec rspec
Daily commands:
bundle exec rake compile # Build the C extension
bundle exec rspec # Run tests (requires MySQL running)
bundle exec ruby benchmark/query_with_mysql_casting.rb # Run benchmarks
🗺️Map of the codebase
lib/mysql2.rb— Entry point that loads all mysql2 components and exposes the public API (Client, Result, Statement classes).ext/mysql2/client.c— Core C extension implementing MySQL connection lifecycle, query execution, and result fetching—the performance-critical path.ext/mysql2/result.c— C extension for result set iteration and type casting, responsible for converting MySQL data to Ruby objects.lib/mysql2/client.rb— Ruby wrapper around the C client extension, handles configuration, connection options, and public query interface.ext/mysql2/extconf.rb— Build configuration and libmysql detection—required to compile the gem against system MySQL libraries.lib/mysql2/result.rb— Ruby wrapper for result enumeration and field metadata, bridges C result objects to Ruby Enumerable.ext/mysql2/statement.c— C extension for prepared statements, handles parameter binding and execution.
🛠️How to make changes
Add a new Result type casting behavior
- Define the casting logic in ext/mysql2/result.c in the mysql2_result_fetch_row function, handling the new MySQL type in the switch statement. (
ext/mysql2/result.c) - If encoding conversion is needed, add charset mapping to ext/mysql2/mysql_enc_to_ruby.h or mysql_enc_name_to_ruby.h. (
ext/mysql2/mysql_enc_to_ruby.h) - Add test cases in spec/mysql2/result_spec.rb to verify the new casting behavior. (
spec/mysql2/result_spec.rb)
Add a new Client connection option
- Parse the option in ext/mysql2/client.c in the mysql2_client_initialize function, then pass it to mysql_options or store it for later use. (
ext/mysql2/client.c) - Document the option in lib/mysql2/client.rb in the initialize method documentation. (
lib/mysql2/client.rb) - Add integration test in spec/mysql2/client_spec.rb to verify the option is applied correctly. (
spec/mysql2/client_spec.rb)
Support a new MySQL data type or statement feature
- Update the statement binding logic in ext/mysql2/statement.c to handle the new type in the bind parameters section. (
ext/mysql2/statement.c) - Update the result casting logic in ext/mysql2/result.c to decode the new type from the result set. (
ext/mysql2/result.c) - Add test coverage in spec/mysql2/statement_spec.rb for parameter binding and in spec/mysql2/result_spec.rb for result decoding. (
spec/mysql2/statement_spec.rb)
Handle a new character encoding or improve encoding support
- Regenerate encoding mapping tables using support/ruby_enc_to_mysql.rb and support/mysql_enc_to_ruby.rb utilities. (
support/mysql_enc_to_ruby.rb) - Update ext/mysql2/mysql_enc_to_ruby.h and ext/mysql2/mysql_enc_name_to_ruby.h with the new mappings. (
ext/mysql2/mysql_enc_to_ruby.h) - Test encoding handling in spec/mysql2/result_spec.rb and verify character set conversion. (
spec/mysql2/result_spec.rb)
🔧Why these technologies
- C extensions (libmysql binding) — Direct native binding to libmysql eliminates Ruby-level I/O overhead and enables high-throughput query execution; the gem is designed for speed-critical use cases like data warehousing and batch processing.
- UTF-8 / encoding-aware API — Forces UTF-8 by default and uses mysql_set_character_set API to ensure consistent character handling across Ruby's Encoding and MySQL's charset system, preventing silent data corruption.
- Lazy result iteration (Enumerable) — Avoids loading entire result sets into memory; enables processing of arbitrarily large query results by fetching rows on-demand.
- Prepared statements (C-level binding) — Provides parameter binding, SQL injection protection, and reusable statement plans at the libmysql level without Ruby marshaling overhead.
⚖️Trade-offs already made
-
Native C extension over pure Ruby
- Why: Performance: C-level calls to libmysql eliminate Ruby-level overhead per query and per row.
- Consequence: Complexity: requires compilation for each platform and Ruby version; harder to debug and modify. Not usable in JRuby or other non-MRI Rubies.
-
Lazy result fetching by default (mysql_use_result vs mysql_store_result)
- Why: Memory efficiency for large result sets; doesn't load all rows into memory at once.
- Consequence: Locks the connection for the lifetime of iteration; cannot issue new queries until iteration is complete. Users must call store_result: true explicitly to buffer results.
-
Type casting in C rather than Ruby
- Why: Speed: avoids Ruby method calls per field per row; casting rules are simpler and more predictable at the MySQL API level.
- Consequence: Encoding details and type coercion are harder to customize or introspect from Ruby code; less flexible than ActiveRecord's type system.
-
No connection pooling in the gem itself
- Why: Scope simplification: connection management (pooling, reconnect logic) is the responsibility of the calling application or ORM.
- Consequence: Users must implement their own pooling (e.g., via Sequel, Rails ConnectionPool, or manual Thread-local storage) for threaded applications.
🚫Non-goals (don't propose these)
- Does not provide query builder, migrations, or ORM abstractions—it is a thin binding only.
- Does not handle connection pooling or thread-safety wrappers—users must manage pooling externally.
- Does not support JRuby, TruffleRuby, or other non-MRI Ruby implementations—requires native C compilation.
- Does not implement query caching or result memoization—each query hits the server.
- Does not provide automatic reconnection on connection loss—caller must handle errors and reconnect.
🪤Traps & gotchas
System MySQL libs required: gem install mysql2 fails silently without libmysqlclient-dev (Ubuntu) or mysql-devel (CentOS)—use --with-mysql-dir or --with-mysql-config flags if auto-detection fails. Threading: raw Mysql2::Client is not thread-safe; multi-threaded apps must use connection pooling (handled by ActiveRecord). Encoding: forces UTF-8 by design; binary data requires explicit handling. MySQL version skew: prepared statements and result casting differ between MySQL 5.7, 8.0, and MariaDB 10.x; CI tests these but local dev may not catch version-specific bugs. OpenSSL path: SSL connections require OpenSSL dev libs; use --with-openssl-dir if linking fails.
🏗️Architecture
💡Concepts to learn
- libmysqlclient C API binding — mysql2 is fundamentally a thin Ruby wrapper over libmysqlclient; understanding the C library's lifecycle (connect, query, fetch, close) and thread semantics is critical to using the gem safely in production
- Prepared Statements & Parameter Binding — mysql2::Statement (ext/mysql2/statement.c) implements MySQL's binary protocol for prepared statements; essential for SQL injection prevention and query optimization in parameterized queries
- Character Encoding & MySQL Protocol Encoding Conversion — mysql2 forces UTF-8 and uses MySQL's encoding-aware API calls (ext/mysql2/mysql_enc_to_ruby.h, mysql_enc_name_to_ruby.h); misunderstanding charset handling causes silent data corruption or query failures
- Ruby C Extension API (rb_*) — All of ext/mysql2/*.c uses Ruby's C API (VALUE, rb_str_new, rb_ary_push, etc.); understanding memory management and type coercion is required to modify the driver
- Thread Safety & Connection Pooling — Mysql2::Client is not thread-safe for concurrent queries; production Rails apps rely on connection pooling (ActiveRecord::ConnectionPool) to serialize access; violating this causes race conditions
- MySQL Result Set Streaming & Memory Efficiency — Mysql2::Result can stream rows via Enumerable or buffer all at once; understanding lazy vs eager fetching (result.each vs result.to_a) is critical for large result sets and memory footprint
- OpenSSL Integration for SSL/TLS Connections — mysql2 links against OpenSSL to support encrypted MySQL connections (ssl_key, ssl_cert, ssl_ca options); version mismatches or missing OpenSSL dev libs break installation
🔗Related repos
rails/rails— ActiveRecord uses mysql2 as the default MySQL adapter for Rails apps; this is the primary consumerjeremyevans/sequel— Sequel ORM supports mysql2 as a first-class database adapter; provides an alternative to ActiveRecord using the same driverruby-mysql/mysql2— Historical alternative MySQL gem (pure Ruby, slower); mysql2 was designed to replace it with C-level performancesocketry/mysql— Newer pure-Ruby async MySQL client; represents different design philosophy (no C extension, async/await-friendly) vs mysql2's sync C bindingbrianmario/bson-ruby— Same maintainer; demonstrates expertise in Ruby C extensions and encoding-aware binary protocols
🪄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 statement parameter binding tests in spec/mysql2/statement_spec.rb
The statement.c and statement.h C extensions handle prepared statements and parameter binding, but the existing spec/mysql2/statement_spec.rb likely lacks coverage for edge cases like NULL bindings, type coercion, binary data, and encoding handling. This is critical for security (SQL injection prevention) and correctness.
- [ ] Review existing spec/mysql2/statement_spec.rb to identify gaps in parameter binding test coverage
- [ ] Add tests for NULL parameter binding and type coercion edge cases
- [ ] Add tests for binary data handling in prepared statements (referenced by ext/mysql2/infile.c patterns)
- [ ] Add tests for multi-byte UTF-8 and encoding-aware bindings (mysql2 forces UTF-8 per README)
- [ ] Add tests for prepared statement reuse and parameter resets
- [ ] Run full test suite to ensure no regressions
Add GitHub Actions workflow for Ruby version matrix testing (Windows support)
The repo has build.yml and container.yml workflows but no explicit Windows CI coverage. Appveyor is referenced in README but appears deprecated. The ext/mysql2 C extensions need Windows-specific compilation testing. A native GitHub Actions matrix workflow for Windows + Ruby versions would modernize CI.
- [ ] Create .github/workflows/windows.yml with windows-latest runner
- [ ] Define Ruby version matrix (3.1, 3.2, 3.3, 3.4+) for Windows testing
- [ ] Add steps to install MySQL development libraries on Windows (vcpkg or pre-installed)
- [ ] Run
rake compileto test C extension compilation on Windows - [ ] Run
bundle exec rspecto validate test suite on Windows - [ ] Document any Windows-specific setup in CONTRIBUTING or README section
Refactor ext/mysql2/client.c error handling into shared utility header
The client.c, result.c, and statement.c files likely duplicate error handling logic (wrapping libmysql errors into Ruby exceptions). Extracting this into a new ext/mysql2/error_utils.h would reduce code duplication, improve maintainability, and ensure consistent error behavior across all modules. This aligns with existing error.c in lib/mysql2/.
- [ ] Audit error handling patterns in ext/mysql2/client.c, ext/mysql2/result.c, and ext/mysql2/statement.c
- [ ] Identify common error wrapping code (mysql_error(), mysql_errno() usage patterns)
- [ ] Create ext/mysql2/error_utils.h with reusable macros/functions for error handling
- [ ] Refactor each C file to use the new utility functions
- [ ] Ensure lib/mysql2/error.rb exception hierarchy is preserved
- [ ] Run full test suite and benchmarks to verify no performance regression
🌿Good first issues
- Add missing RSpec tests for
Mysql2::Client#preparewith edge cases (NULL parameters, very large data, binary data);ext/mysql2/statement.cis well-tested but Ruby wrapper inlib/mysql2/client.rbcould use more coverage. - Extend
benchmark/suite: add a benchmark comparing Mysql2 result casting performance (cast_booleans: truevsfalse,symbolize_keysoverhead); helps future optimizations and documents real-world costs. - Document threading safety guarantees in
lib/mysql2/client.rbdocstrings; currently unclear whether query_timeout, connection_options, or result streaming are safe in multi-threaded contexts—add explicit warnings and examples.
⭐Top contributors
Click to expand
Top contributors
- @sodabrew — 31 commits
- @mishina2228 — 10 commits
- @tenderlove — 8 commits
- @byroot — 7 commits
- @junaruga — 6 commits
📝Recent commits
Click to expand
Recent commits
e0f5cd9— CI: drop mysql55 and mysql57 setup scripts (#1440) (fkmy)26ea201— CI: Add Ruby 4.0 (#1439) (fkmy)fae86fb— CI: drop ubuntu-20.04 and centos:7 (#1438) (fkmy)b7f9558— Fix compilation with C23 compilers (#1430) (nicholasdower)18636c6— MariaDB determine json from Extended Metadata (#1423) (grooverdan)f828ba5— CI: upgrade actions/checkout to v6 (#1437) (fkmy)5604321— CI: update support/B7B3B788A8D3785C.asc (#1424) (grooverdan)770ec66— Rubocop updates in specs (#1434) (byroot)b009d7e— Fix Client#affected_rows for queries with no affected rows (#1417) (byroot)387e13a— chore(style): use Ruby 3.4 to run RuboCop with the latest version (#1408) (tnir)
🔒Security observations
The mysql2 gem demonstrates good security practices overall as a low-level database driver. Main concerns are: (1) SSL test certificates and private keys stored in version control, (2) potential for developers to misuse the library without proper parameterized queries, (3) example configuration files that could be misused, and (4) lack of formal security disclosure process. The library itself is a C binding to libmysql and doesn't introduce SQL
- Medium · Native C Extension Without Input Validation Framework —
ext/mysql2/ (client.c, result.c, statement.c, infile.c). The mysql2 gem includes C extensions (ext/mysql2/client.c, result.c, statement.c) that directly interface with libmysql. While the library itself doesn't construct SQL, applications using this library must implement their own parameterized query protection. The C code handles low-level operations and developers relying on this library need to ensure proper query parameterization at the application level. Fix: Document security best practices prominently in README. Ensure all examples use parameterized queries/prepared statements. Add security warnings in the gem documentation about SQL injection risks when using string interpolation. - Medium · SSL Certificate Files in Repository —
spec/ssl/pkcs8-server-key.pem, spec/ssl/server-key.pem, spec/ssl/client-key.pem, spec/ssl/ca-key.pem. Test SSL certificates and private keys are stored in the repository (spec/ssl/ directory). While these are test certificates, storing private keys in version control is a security anti-pattern that could be replicated in production environments by developers learning from this codebase. Fix: Move test certificates to a separate secure location or generate them dynamically during test setup. Document that production certificates should never be committed to version control. Consider using a pre-commit hook to prevent accidental key commits. - Medium · Configuration Files with Sensitive Data Patterns —
spec/configuration.yml.example, spec/my.cnf.example. Example configuration files (spec/configuration.yml.example, spec/my.cnf.example) exist. If developers copy these to actual config files without changing credentials, sensitive database connection details could be exposed. Fix: Ensure example files clearly indicate they are templates. Document that sensitive credentials should be managed via environment variables or secure credential management systems. Add .example files to .gitignore enforcement documentation. - Low · Potential Hardcoded Test Database Credentials —
ci/setup.sh, ci/setup_container.sh, ci/ssl.sh, ci/Dockerfile_fedora. Shell scripts in ci/ directory (ci/setup.sh, ci/setup_container.sh, ci/ssl.sh) may contain database connection parameters or credentials. These could serve as examples for insecure practices. Fix: Ensure CI scripts use environment variables for all credentials. Document secure CI/CD practices. Remove any hardcoded passwords or connection strings from scripts. - Low · Docker Configuration Security Hardening —
ci/Dockerfile_fedora, ci/container.sh, ci/setup_container.sh. Dockerfile_fedora and container setup scripts should follow security best practices. Without seeing the full content, potential issues could include running as root, missing security options, or outdated base images. Fix: Implement Docker security best practices: run as non-root user, use specific base image versions, add security scanning to CI/CD pipeline, use multi-stage builds where applicable. - Low · Missing SECURITY.md File —
Repository root. The repository lacks a SECURITY.md file with vulnerability disclosure guidelines. This makes it difficult for security researchers to report issues responsibly. Fix: Create SECURITY.md with responsible disclosure policy, contact information for security issues, and version support information. - Low · Public Key Files in Repository —
support/3A79BD29.asc, support/5072E1F5.asc, support/B7B3B788A8D3785C.asc. GPG public keys stored in support/ directory (.asc files) suggest the project signs releases, which is good. However, ensure these are legitimately the project's keys and not accidentally committed private keys. Fix: Verify these are indeed public keys only. Document the key IDs and their purposes. Consider linking to a public key server or security documentation.
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.