Skip to content

fix: close build and fetch spans on their error paths - #101

Merged
jaronoff97 merged 2 commits into
masterfrom
fix/span-failure-terminator
Sep 15, 2026
Merged

jaronoff97 merged 2 commits into
masterfrom
fix/span-failure-terminator

Conversation

@jaronoff97

Copy link
Copy Markdown
Contributor

Closes #100. Stacked on #99, so it targets that branch. Retarget to master once #99 merges.

The problem

buildIndex starts a span and only terminates it on success:

var span = bus.started(.info, started_event);

if (policies_slice.len > max_policies) return error.TooManyPolicies;  // span left open
for (policies_slice, 0..) |*policy, i| {
    try builder.processPolicy(policy, @intCast(i));                   // same
}
var index = try builder.finish();                                     // same

span.completed(completed_event);

Three ways out leave the span dangling. This is pre-existing and was identical in all three of the old per-signal build functions. #99 factored those into one function, which is why this is now a single-site fix.

HttpProvider.fetchAndNotify has the other half of the problem. It uses defer span.completed(...), so the span always closes, but a failed fetch is reported as an ordinary completion.

Neither site could do better, because SpanGuard had only completed and done. There was no way to say the span ended badly.

The fix

SpanGuard.failed(err) records the error name and emits at .err, so a failure stays visible even when the span itself is a debug span.

The first terminator wins. The guard now tracks whether it has been terminated, and later terminators no-op. That makes the natural pairing safe:

defer span.completed(fetch_completed);   // registered first, runs last
errdefer |err| span.failed(err);         // registered second, runs first

On the error path the failure is recorded and the completion that follows is a no-op. On the success path only the completion runs. Without the flag this pairing would emit two contradictory terminations.

Both span sites now use errdefer |err| span.failed(err).

Scope

A sweep of started( across src/ found exactly two call sites, both fixed here. No other span has this gap.

Tests

  • A failed span emits the error name and the elapsed time.
  • The first terminator wins: after failed, a following completed and done emit nothing.

Verification

  • zig build test -Doptimize=ReleaseSafe passes.
  • Zig 0.16.0 on macOS arm64.

🤖 Generated with Claude Code

@macroscopeapp

macroscopeapp Bot commented Sep 15, 2026

Copy link
Copy Markdown

Approvability

Verdict: Would Approve

Macroscope's review found this PR approvable — This is a focused observability bug fix that closes existing build and fetch spans on errors without changing successful processing behavior. The implementation is localized and includes tests for failure reporting and duplicate termination prevention.

Not approved because:

  • Monthly spending limit reached (workspace setting). Approvability relies on correctness review in order to determine eligibility

No code changes detected at 502efcc. Prior analysis still applies.

Review your spending limits in Billing settings. You can add or adjust custom eligibility rules. Learn more.

@jaronoff97
jaronoff97 added this pull request to stack #102 September 15, 2026 02:46
smithclay
smithclay previously approved these changes Sep 15, 2026
Base automatically changed from fix/matcher-index-allocation-failures to master September 15, 2026 16:12
@jaronoff97
jaronoff97 dismissed smithclay’s stale review September 15, 2026 16:12

The merge-base changed after approval.

jaronoff97 and others added 2 commits September 15, 2026 12:12
Closes #100.

`buildIndex` started a span and only terminated it on success, so
`TooManyPolicies`, a failed `processPolicy`, and a failed `finish` all
left the span open in the telemetry. `HttpProvider.fetchAndNotify` always
terminated its span, but reported a failed fetch as an ordinary
completion.

`SpanGuard` had no failure terminator, only `completed` and `done`, so
neither site could record what actually happened.

- Add `SpanGuard.failed(err)`, which records the error name and emits at
  `.err` so a failure stays visible even on a debug span.
- Track termination in the guard. The first terminator wins and later
  ones no-op, so a `defer completed` paired with an `errdefer failed`
  reports the failure instead of contradicting it.
- Use `errdefer |err| span.failed(err)` at both span sites.

A sweep of `started(` found only these two call sites, so no other span
has this gap.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Satisfies ziglint Z010.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jaronoff97
jaronoff97 force-pushed the fix/span-failure-terminator branch from aee8c13 to 502efcc Compare September 15, 2026 16:12
@jaronoff97
jaronoff97 merged commit d3d0f4e into master Sep 15, 2026
6 checks passed
@jaronoff97
jaronoff97 deleted the fix/span-failure-terminator branch September 15, 2026 16:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Matcher-index build spans dangle on every error path

2 participants