feat(#3345): improve error messages for Rego compilation failures#3381
Conversation
|
🤖 Finished Review · ✅ Success · Started 8:53 AM UTC · Completed 8:59 AM UTC |
Review — commentPR: feat(#3345): improve error messages for Rego compilation failures The change is well-scoped to issue #3345 and addresses all four acceptance criteria. The architectural placement in Findings1. Misleading error message for non-builtin compilation errors —
|
| File | Assessment |
|---|---|
internal/evaluator/rego_errors.go |
✅ New file — clean implementation with isRegoCompilationError, bundledOPAVersion, and wrapRegoError helpers. Nil handling, error chain preservation via %w, and graceful "unknown" fallback for build info are all correct. |
internal/evaluator/rego_errors_test.go |
✅ Thorough unit tests covering all code paths: nil errors, each rego error type, non-rego passthrough, version lookup with mocked readBuildInfo, unknown version message adjustments, and errors.Is chain preservation. Build tag //go:build unit matches project conventions. |
internal/evaluator/conftest_evaluator.go |
✅ Two wrapRegoError calls added at the correct error paths in conftestRunner.Run (after TestRunner.Run and after LoadWithData). The disallowedBuiltins extraction from the inline set to the shared variable in rego_errors.go is a clean DRY improvement — the comment cross-references the source of truth. |
internal/evaluator/opa_evaluator.go |
✅ Single wrapRegoError call wraps the LoadWithData error. The existing fmt.Errorf("load: %w", err) prefix is preserved inside the wrapped message's Details section, which is correct. |
features/__snapshots__/validate_image.snap |
✅ Snapshot updated to reflect the new error format. The v1.15.2 version matches go.mod. |
Correctness
- Error chain integrity: All three wrapping sites use named returns or direct
return, andwrapRegoErrorusesfmt.Errorfwith%wsoerrors.Is/errors.Ascontinue to work through the chain. Verified no double-wrapping occurs — each error path has exactly onewrapRegoErrorcall. - Non-rego passthrough:
wrapRegoErrorreturns the original error unchanged for non-matching errors, so unrelated error paths are unaffected. disallowedBuiltinsconsistency: The sharedvarslice is used by bothstrictCapabilities(capability filtering) andwrapRegoError(error message), ensuring the lists stay in sync.- No missing call sites: Verified all
conftest.LoadWithDatacalls in both evaluators and theTestRunner.Runcall in the conftest evaluator are covered. Theconftest.LoadCapabilitiesand runtime evaluation paths correctly do not wrap, since they don't produce rego compilation errors.
Security
- No new trust boundaries, external inputs, or privilege changes.
- The
disallowedBuiltinsrefactor preserves the existing security restrictions (disablingopa.runtime,http.send,net.lookup_ip_addr) without modification. - PR body and issue text contain no injection patterns.
Findings
[low] Error message wording is overly specific for parse/compile errors — rego_errors.go:80
The primary message says "the policy references Rego built-in functions not available in this version", which accurately describes rego_type_error: undefined function cases but is misleading for rego_parse_error (syntax issues) or rego_compile_error (general compilation failures) where the root cause may not involve built-in functions at all. The "less common causes" section does mention syntax, but it's framed as secondary.
A more accurate primary message might be: "the policy could not be compiled with the OPA version bundled in this version of Conforma CLI" — generic enough for all three error types while still directing the user toward version compatibility.
This is non-blocking because the original error is always preserved in the Details section, and users can diagnose the actual cause from there. Consider as a follow-up improvement.
Previous run (2)
Review — PR #3381
feat(#3345): improve error messages for Rego compilation failures
Summary
This PR implements the error-wrapping feature requested in #3345. It intercepts rego_type_error, rego_parse_error, and rego_compile_error from OPA/conftest compilation and wraps them with a user-friendly message including the bundled OPA version, a list of restricted built-in functions, and remediation guidance. The original error is preserved via %w for debugging. All four acceptance criteria from the issue are met.
The implementation is clean: a new rego_errors.go file isolates the error-wrapping logic, the disallowedBuiltins list is extracted as a shared single-source-of-truth, and the existing readBuildInfo override pattern (from internal/version/version.go) is reused. Tests are thorough — covering all three error patterns, nil/non-rego passthrough, unknown-version graceful degradation, and error chain preservation.
Findings
1. disallowedBuiltins as mutable package-level var weakens defense-in-depth — medium / security
File: internal/evaluator/rego_errors.go, line 32
The security-critical disallowedBuiltins list — which controls which OPA built-in functions are blocked in the sandbox (opa.runtime, http.send, net.lookup_ip_addr) — was previously a local variable inside strictCapabilities(), inaccessible to other code. This PR promotes it to a mutable package-level var ([]string slice).
As a package-level var, any code in the evaluator package can mutate or reassign it at runtime. If the slice were emptied (e.g., disallowedBuiltins = disallowedBuiltins[:0]), strictCapabilities() would call sets.NewString() with no arguments, producing an empty set — meaning no builtins would be filtered, re-enabling opa.runtime (environment variable access) and http.send/net.lookup_ip_addr (network access). This is a fail-open condition.
The PR itself establishes the readBuildInfo var as test-injectable in this same file, creating a convention that encourages mutation of package-level vars.
Remediation: Replace the mutable var with a function returning a fresh slice copy:
func disallowedBuiltinNames() []string {
return []string{"opa.runtime", "http.send", "net.lookup_ip_addr"}
}Update strictCapabilities to sets.NewString(disallowedBuiltinNames()...) and wrapRegoError to strings.Join(disallowedBuiltinNames(), ", "). This prevents any caller from mutating the canonical list while preserving the single-source-of-truth benefit.
2. Multi-line original error indentation is inconsistent — low / error-handling
File: internal/evaluator/rego_errors.go, line 95
The format string "Original error:\n %w" indents only the first line of the original error with two spaces. For multi-line errors (the common case — multiple rego_type_error lines), subsequent lines appear unindented, creating visual inconsistency in the output. The snapshot test confirms this is the current behavior.
Remediation: Consider splitting the original error by newlines and indenting each line, or document this as intentional.
Verdict
comment-only — The medium-severity security finding about the mutable disallowedBuiltins var is worth noting but does not block the PR. The current code is functionally correct and the risk requires an intentional modification within the same package. The change is well-scoped, well-tested, and achieves its stated goal.
Previous run (3)
Review — approve
PR: #3381 — feat(#3345): improve error messages for Rego compilation failures
Summary
This PR intercepts rego_type_error, rego_parse_error, and rego_compile_error from OPA/conftest compilation and wraps them with user-friendly messages that include the bundled OPA version and remediation guidance. The original error is preserved via %w wrapping.
Changes reviewed
| File | Assessment |
|---|---|
internal/evaluator/rego_errors.go |
New file — clean implementation with isRegoCompilationError, bundledOPAVersion, and wrapRegoError helpers. The disallowedBuiltins variable provides a single source of truth shared with strictCapabilities. |
internal/evaluator/rego_errors_test.go |
Comprehensive unit tests covering nil/empty errors, all three rego error types, multi-error messages, build info edge cases, and error wrapping preservation via errors.Is. |
internal/evaluator/conftest_evaluator.go |
wrapRegoError applied at both error paths in conftestRunner.Run (after TestRunner.Run() and LoadWithData()). The disallowedBuiltins list is correctly extracted from inline literals to the shared variable. |
internal/evaluator/opa_evaluator.go |
wrapRegoError applied around the LoadWithData error in compileEngine. |
features/__snapshots__/validate_image.snap |
Snapshot updated to reflect the new wrapped error format. |
Findings
No blocking findings. The implementation directly satisfies all four acceptance criteria from issue #3345:
- ✅ Produces helpful, actionable error messages for Rego compilation failures
- ✅ Includes the bundled OPA version number (
v1.15.2) - ✅ Suggests potential solutions (upgrade CLI, check restricted builtins, adjust policy syntax)
- ✅ Preserves the original compiler error for debugging (via
fmt.Errorf %w)
Notes
- Error detection approach: String-matching on
err.Error()for rego error patterns is pragmatic — OPA doesn't expose typed errors for these compilation failures, so substring matching is the correct approach here. - Single source of truth: Extracting
disallowedBuiltinsinto a shared package-level variable is a good refactoring. Thesets.NewString(disallowedBuiltins...)call instrictCapabilitiescreates a copy via spread, so the original slice is not mutated. - Test design: The
readBuildInfovariable injection for testingbundledOPAVersionfollows standard Go patterns and avoids the need for integration tests against actual build metadata. - Snapshot maintenance: The
v1.15.2version in the acceptance test snapshot will need updating when the OPA dependency is bumped. This is standard snapshot test behavior, documented inAGENTS.md(UPDATE_SNAPS=true make acceptance).
Previous run (4)
Review — PR #3381
Verdict: request-changes
Overview
This PR adds user-friendly error wrapping for OPA/Rego compilation failures, fulfilling all four acceptance criteria from issue #3345. The implementation is clean and well-tested:
- New
rego_errors.gowithisRegoCompilationError,bundledOPAVersion, andwrapRegoErrorhelper functions - Error wrapping applied at three compilation error sites (2 in
conftest_evaluator.go, 1 inopa_evaluator.go) - Original error preserved via
fmt.Errorf %wwrapping - 186 lines of unit tests covering all functions, edge cases, and error chain preservation
- Test injection pattern (
var readBuildInfo) matches existing convention ininternal/version/version.go:35
The code logic is correct. However, there is one blocking issue.
Findings
🔴 HIGH — Acceptance test snapshot not updated
File: features/__snapshots__/validate_image.snap:2166-2170
The "Dropping rego capabilities" acceptance test (features/validate_image.feature:799) uses snapshot matching on stderr. The current snapshot expects the raw OPA error format:
Error: error validating image .../acceptance/ec-happy-day of component Unnamed: load: loading policies: get compiler: 3 errors occurred:
.../main.rego:14: rego_type_error: undefined function opa.runtime
.../main.rego:22: rego_type_error: undefined function http.send
.../main.rego:33: rego_type_error: undefined function net.lookup_ip_addr
After this PR, wrapRegoError intercepts these errors and produces a completely different message starting with policy compilation error: the policy uses Rego features or built-in functions not available.... The snapshot must be updated (e.g., via UPDATE_SNAPS=true make acceptance) for this test to pass.
Remediation: Run the acceptance test with UPDATE_SNAPS=true and commit the updated snapshot file. This is also an opportunity to verify that the improved error message renders well in the full end-to-end context.
🟡 LOW — Error message refers to "ec-cli" instead of current project name
File: internal/evaluator/rego_errors.go:72-76
The wrapped error message says "the one bundled in ec-cli" and "Upgrade ec-cli". The project has been renamed — cmd/root/root_cmd.go:69 identifies it as "Conforma CLI". User-facing error messages should use the current name for consistency.
Remediation: Replace ec-cli with conforma (the CLI binary name) or Conforma CLI in the error message template.
🟡 LOW — Hardcoded restricted-function list creates maintenance coupling
File: internal/evaluator/rego_errors.go:74
The error message hardcodes "opa.runtime, http.send, net.lookup_ip_addr" as example restricted builtins. The authoritative disallowed list lives in strictCapabilities() at conftest_evaluator.go:1199-1205. If the disallowed set changes, this error message becomes stale. The "e.g." qualifier provides some cover, but could still mislead users.
Remediation (follow-up): Consider extracting the disallowed builtins into a shared package-level variable that both strictCapabilities() and wrapRegoError() reference, or add a code comment cross-referencing the two locations.
🟡 LOW — ec test command path not covered
File: cmd/test/test.go
The ec test command calls conftest's runner.TestRunner.Run() directly without going through the wrapped conftestRunner.Run(). Users running ec test with incompatible policies will still see raw rego errors. This is a minor gap since ec test is a conftest-compatibility command, but it could confuse users who see different error quality between ec validate and ec test.
🟡 LOW — Only two of five OPA error categories are matched
File: internal/evaluator/rego_errors.go:40
isRegoCompilationError detects rego_type_error and rego_parse_error. OPA also defines rego_compile_error, rego_unsafe_var_error, and rego_recursion_error. While the two covered patterns are the most common for version/capability mismatches, a rego_compile_error from other causes would not get the improved message. Consider adding rego_compile_error at minimum.
Labels: PR modifies evaluator error handling for improved UX
Previous run (5)
Review of PR #3381 — Improve error messages for Rego compilation failures
Verdict: request-changes — Two medium-severity findings that affect the PR's intended outcome and CI stability.
Summary
This PR adds a new rego_errors.go file with helper functions (isRegoCompilationError, bundledOPAVersion, wrapRegoError) that intercept rego_type_error and rego_parse_error from OPA/conftest compilation and wrap them with a user-friendly message including the bundled OPA version and remediation guidance. The wrapping is applied in three error paths: two in conftest_evaluator.go and one in opa_evaluator.go. Unit tests are comprehensive, and the %w wrapping correctly preserves the error chain.
The implementation is clean, well-structured, and addresses the core UX need described in #3345. However, two issues need resolution before merge.
Findings
1. Acceptance test snapshot not updated (medium — correctness)
File: features/__snapshots__/validate_image.snap (lines 2166–2170)
The "Dropping rego capabilities" acceptance test scenario (features/validate_image.feature:799) validates that ec validate image fails with raw rego_type_error messages when policies use sandboxed functions (opa.runtime, http.send, net.lookup_ip_addr). The snapshot currently expects:
Error: error validating image ... : load: loading policies: get compiler: 3 errors occurred:
.../main.rego:14: rego_type_error: undefined function opa.runtime
...
After this PR, wrapRegoError intercepts this error in conftestRunner.Run() (line 224), wrapping it with the "policy compilation error" message. The snapshot is not updated to reflect this change, so the acceptance test will fail in CI.
Remediation: Run acceptance tests with UPDATE_SNAPS=true make acceptance to regenerate the snapshot, or manually update the snapshot entry for [TestFeatures/Dropping rego capabilities:stderr - 1] to match the new wrapped error format.
2. Misleading error message for intentional capability restrictions (medium — correctness)
File: internal/evaluator/rego_errors.go (lines 62–73)
The wrapRegoError function attributes all rego_type_error occurrences to OPA version incompatibility:
"the policy uses Rego features or built-in functions not supported by the bundled OPA version (v1.15.2). This is likely caused by a version mismatch..."
However, rego_type_error also occurs when the strict capabilities sandbox intentionally strips dangerous built-ins (opa.runtime, http.send, net.lookup_ip_addr) — see strictCapabilities() in conftest_evaluator.go:1183. In this case the error is not a version mismatch; it's an intentional security restriction. A user whose policy calls opa.runtime() would be told to "upgrade ec-cli" when the actual issue is that the function is disallowed by design.
This is the exact error path validated by the "Dropping rego capabilities" acceptance test, so it's a realistic user scenario.
Remediation: Consider distinguishing between capability-restricted functions and genuinely unsupported features. For example, check whether the error mentions functions in the disallowed set from strictCapabilities() and produce a different message (e.g., "this function is restricted for security reasons"), or narrow the wrapping to only apply in opa_evaluator.go / error paths where capabilities are not being artificially restricted. Alternatively, adjust the message to mention capability restrictions as another possible cause.
What looks good
- Error chain preservation: Using
fmt.Errorfwith%wcorrectly preserves the original error forerrors.Is/errors.Asunwrapping. - Test coverage: Unit tests cover nil errors, both error patterns, multi-error messages, non-matching errors, and build info edge cases. The
readBuildInfovariable pattern for test injection is clean. - Defensive nil handling:
isRegoCompilationError(nil)correctly returnsfalse, makingwrapRegoError(nil)safe. - Scope: The change is well-contained — only error wrapping is added, no behavioral changes to the evaluation pipeline itself.
Labels: PR modifies evaluator error handling and touches UX-facing error messages
|
/fs-fix |
|
🤖 Finished Fix · ✅ Success · Started 12:00 PM UTC · Completed 12:12 PM UTC |
Update wrapRegoError to mention capability restrictions (opa.runtime, http.send, net.lookup_ip_addr) as a possible cause alongside version mismatch, so users hitting the security sandbox are not misled into thinking they need to upgrade ec-cli. Addresses review feedback on #3381
|
🤖 Finished Review · ✅ Success · Started 12:12 PM UTC · Completed 12:25 PM UTC |
|
/fs-fix |
|
🤖 Finished Fix · ✅ Success · Started 9:56 AM UTC · Completed 10:15 AM UTC |
- Update acceptance test snapshot for 'Dropping rego capabilities' to match the new wrapped error message format - Replace 'ec-cli' with 'Conforma CLI' in user-facing error messages - Extract disallowed builtins into a shared variable (disallowedBuiltins) used by both strictCapabilities and wrapRegoError, eliminating the maintenance coupling of hardcoded lists - Add rego_compile_error to the detection patterns in isRegoCompilationError for broader OPA error coverage - Add unit tests for rego_compile_error detection and wrapping Addresses review feedback on #3381
|
🤖 Finished Review · ✅ Success · Started 10:16 AM UTC · Completed 10:20 AM UTC |
Superseded by updated review
Codecov Report❌ Patch coverage is
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 4 files with indirect coverage changes 🚀 New features to boost your workflow:
|
|
/fs-fix the outstanding items from my previous review:
|
|
🤖 Finished Fix · ✅ Success · Started 10:30 AM UTC · Completed 10:48 AM UTC |
- Conditionally omit version-specific lines in wrapRegoError when bundledOPAVersion returns "unknown" instead of showing awkward "OPA unknown" / "(unknown)" text - Add two test subtests for the unknown version path: build info unavailable and OPA not in deps - Add missing assert.ErrorIs assertion in multi-error subtest Addresses review feedback on #3381
|
🤖 Finished Review · ✅ Success · Started 10:48 AM UTC · Completed 11:00 AM UTC |
|
/fs-fix address my comment above |
|
🤖 Finished Fix · ✅ Success · Started 12:16 PM UTC · Completed 12:40 PM UTC |
Apply reviewer's suggested message format that leads with the most likely remediation (upgrade Conforma CLI), moves restricted builtins and OPA syntax to secondary 'less common causes' section, and renames 'Original error' to 'Details'. Update test assertions and acceptance test snapshot to match. Addresses review feedback on #3381
|
🤖 Finished Review · ✅ Success · Started 12:41 PM UTC · Completed 12:48 PM UTC |
|
/fs-fix squash all commits into one. Also add a single no-op whitespace change (a space or newline) anywhere in the code, just to produce a non-empty file diff. No behavioral, logic, or message changes |
|
🤖 Finished Fix · ✅ Success · Started 2:03 PM UTC · Completed 2:14 PM UTC |
Intercept rego_type_error, rego_parse_error, and rego_compile_error from OPA/conftest compilation and wrap them with a user-friendly message that includes the bundled OPA version and remediation guidance. Changes: - Add internal/evaluator/rego_errors.go with isRegoCompilationError, bundledOPAVersion, and wrapRegoError helper functions - Apply wrapRegoError in conftest_evaluator.go error paths from TestRunner.Run() and LoadWithData() - Apply wrapRegoError in opa_evaluator.go error path from LoadWithData() - Add unit tests for all new functions covering rego and non-rego error cases, OPA version lookup, and error preservation The original low-level error is preserved via fmt.Errorf %w wrapping so it remains available for debugging. Closes #3345
6a0a02c to
69c24a9
Compare
|
🤖 Finished Review · ✅ Success · Started 2:15 PM UTC · Completed 2:30 PM UTC |
|
🤖 Finished Retro · ✅ Success · Started 2:36 PM UTC · Completed 2:45 PM UTC |
|
PR #3381 added user-friendly error messages for Rego compilation failures in conforma/cli. The code agent created a well-structured implementation but used the stale project name 'ec-cli' (sourced from issue #3345's description) instead of 'Conforma CLI'. The PR required 5 fix iterations over 2 days before merge — at least 2 were avoidable with better agent behavior. The review agent provided genuine value (catching the capability-restriction correctness issue early and the naming problem) but repeated the mutable-state finding 3 times across reviews on unchanged code. The human reviewer uniquely caught edge cases (unknown version handling, error message information hierarchy) that agents missed across multiple runs. Proposals filed
|
Intercept rego_type_error and rego_parse_error from OPA/conftest compilation and wrap them with a user-friendly message that includes the bundled OPA version and remediation guidance.
Changes:
bundledOPAVersion, and wrapRegoError helper functions
TestRunner.Run() and LoadWithData()
LoadWithData()
error cases, OPA version lookup, and error preservation
The original low-level error is preserved via fmt.Errorf %w wrapping so it remains available for debugging.
Closes #3345
Post-script verification
agent/3345-rego-error-messages)d134e495b18cf8493cfea8791e310b58f0320797..HEAD)