GitHub Actions integration¶
Drop VBAlidator into the CI of any repository that holds VBA modules. This page collects copy-pasteable workflow recipes from "smallest possible gate" to "full PR-annotation + artifact + multi-host matrix".
The CLI exits non-zero on errors or below the score threshold, so
every example below is a single shell line; nothing GitHub-specific
needs to be wired up beyond the usual actions/checkout.
TL;DR — the 5-line gate¶
Add this to .github/workflows/vba-precheck.yml in your VBA repo:
name: VBA precheck
on: [push, pull_request]
jobs:
precheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: "3.12" }
- run: pip install vbalidator
- run: vbalidator ./vba --host excel --score-threshold 90
Replace ./vba with the folder that holds your .bas / .cls /
.frm exports and pick the host that matches your codebase
(excel / word / access / outlook / visio). The job fails
when any module emits an error or the confidence score falls below
the threshold.
Recipe 1 — Trigger only on VBA changes¶
Path filters keep the workflow off PRs that only touch READMEs or unrelated code:
name: VBA precheck
on:
push:
paths: ["**/*.bas", "**/*.cls", "**/*.frm"]
pull_request:
paths: ["**/*.bas", "**/*.cls", "**/*.frm"]
jobs:
precheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: "3.12", cache: "pip" }
- run: pip install vbalidator
- run: vbalidator ./vba --host excel --score-threshold 90
cache: "pip" shaves ~3 s off subsequent runs by reusing the wheel
download.
Recipe 2 — Docker (no Python install needed)¶
If your repo never needs Python for anything else, the Docker image is the leanest option:
name: VBA precheck
on: [push, pull_request]
jobs:
precheck:
runs-on: ubuntu-latest
container:
image: ghcr.io/twobeass/vbalidator:latest
steps:
- uses: actions/checkout@v4
- run: vbalidator . --host excel --score-threshold 90
The image is multi-arch (amd64 + arm64) and rebuilt on every
release, so :latest always tracks the most recent PyPI version.
Pin to a specific tag (:v1.6.1) if you want byte-stable runs.
Recipe 3 — Upload the JSON v2 report as an artifact¶
Useful for postmortems and for downstream tools that consume the canonical report:
- run: vbalidator ./vba --host excel --output vba_report.json
- uses: actions/upload-artifact@v4
if: always() # also upload on failure
with:
name: vba-report
path: vba_report.json
The report shape is documented under
Usage → Issue shape (JSON v2) — every
finding carries a stable rule_id you can pin in dashboards.
Recipe 4 — PR-level annotations (line-pinned errors)¶
GitHub renders ::error file=X,line=N::message lines as inline
review comments. The JSON v2 report carries everything we need:
- name: Run VBAlidator
run: |
vbalidator ./vba --host excel --quiet --output vba_report.json || true
- name: Annotate PR with findings
run: |
python -c '
import json
for i in json.load(open("vba_report.json"))["issues"]:
sev = i["severity"]
kind = "error" if sev in ("error", "compile_verified") else "warning"
print(f"::{kind} file={i[\"file\"]},line={i[\"line\"]}::"
f"[{i[\"rule_id\"]}] {i[\"message\"]}")
'
- name: Re-fail on errors
run: |
python -c '
import json, sys
r = json.load(open("vba_report.json"))
if not r["summary"]["compile_safe"]:
sys.exit(1)
'
The || true after the first vbalidator call keeps the workflow
running so the annotation step can fire even when there are errors.
The last step rechecks the JSON and fails the job — annotations
without a real exit code don't block the merge.
Recipe 5 — Matrix over multiple hosts¶
If your repo holds VBA for several Office hosts side-by-side
(say vba/excel/ and vba/word/):
jobs:
precheck:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- { host: excel, path: vba/excel }
- { host: word, path: vba/word }
- { host: access, path: vba/access }
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: "3.12", cache: "pip" }
- run: pip install vbalidator
- run: vbalidator ${{ matrix.path }} --host ${{ matrix.host }} --score-threshold 90
fail-fast: false lets the other matrix entries run to completion
even when one host fails — you get one red square per problematic
codebase, not just the first.
Recipe 6 — Step Summary for human review¶
$GITHUB_STEP_SUMMARY renders Markdown on the workflow summary page.
Drop a compact verdict so reviewers don't need to dig through logs:
- name: Run VBAlidator
run: vbalidator ./vba --host excel --output vba_report.json --quiet || true
- name: Step summary
run: |
python <<'PY' >> "$GITHUB_STEP_SUMMARY"
import json
r = json.load(open("vba_report.json"))
s = r["summary"]
icon = "✅" if s["compile_safe"] else "❌"
print(f"# {icon} VBAlidator — score {s['score']} / 100")
print()
print(f"| Severity | Count |")
print(f"|---|--:|")
print(f"| errors | {s['errors']} |")
print(f"| warnings | {s['warnings']} |")
print(f"| info | {s['info']} |")
print()
if r["issues"]:
print("## Findings")
print("| File | Line | Rule | Message |")
print("|---|--:|---|---|")
for i in r["issues"][:50]:
print(f"| {i['file']} | {i['line']} | {i['rule_id']} | {i['message']} |")
PY
- name: Re-fail on errors
run: |
python -c 'import json,sys; sys.exit(0 if json.load(open("vba_report.json"))["summary"]["compile_safe"] else 1)'
Recipe 7 — Local custom model alongside the bundled host¶
If your project has its own .tlb or references a class the bundled
host model doesn't ship (e.g. a private add-in), drop a
vba_model.json next to your VBA folder and VBAlidator auto-loads it:
Or be explicit:
See Configuration → Custom models
for the schema and Configuration → Bundled host models
for the seven auto-layered COM-companion stubs (scripting,
vbscript_regexp, wscript_shell, shell_application, mscomctl,
msforms, plus the five Office hosts) — most repos don't need a
custom model because the auto-layering already covers
Scripting.Dictionary, VBScript.RegExp, MSForms UserForms, etc.
Recipe 8 — Status badge in your VBA repo's README¶
[](https://github.com/<your-org>/<your-repo>/actions/workflows/vba-precheck.yml)
What VBAlidator catches (one-line recap)¶
Every emitted finding carries a rule_id you can pin in jq filters
or PR ignore-lists. The catalogue is at Rules. The
high-impact subset for AI-generated VBA:
- VBA001 undefined identifiers, VBA002 missing object members
- VBA006 wrong argument count, VBA007 ByRef type mismatches
- VBA210
Seton scalar, VBA211 missingSeton object assignment - VBA221–VBA224 Property Get/Let/Set arity & semantics
- VBA230 / VBA231 non-constant Const initialisers
- VBA300 missing
PtrSafeon 64-bit Office Declares - VBA320 missing
Option Explicit(warning) - VBA330 incomplete
Implementsof an interface - VBA340 / VBA341
RaiseEventwithout matchingEventdeclaration / wrong arity - VBA_LEX001 / VBA_LEX002 unrecognised characters & malformed date literals
- VBA_RT001 (optional) errors caught by the actual VBE compiler via Office round-trip
When you want VBE itself in the loop¶
The CLI's --roundtrip flag drives the actual VBE compiler over
Office COM for a second-opinion check, but that requires a Windows
runner with Office and pywin32 installed — GitHub-hosted
windows-latest doesn't ship Office. The setup is documented under
CI/CD → Self-hosted Windows runner for repos that have
the infrastructure.
For most repos the static analysis is enough; the round-trip is a nice-to-have, not a requirement.