Skip to content

Fix error counter keying on dynamic error messages#182

Open
andrewwormald wants to merge 5 commits into
mainfrom
fix/error-counter-key-stability
Open

Fix error counter keying on dynamic error messages#182
andrewwormald wants to merge 5 commits into
mainfrom
fix/error-counter-key-stability

Conversation

@andrewwormald
Copy link
Copy Markdown
Collaborator

@andrewwormald andrewwormald commented Mar 10, 2026

Problem

Error counter uses err.Error() as part of the map key. Errors with dynamic content (timestamps, IDs) create unique keys, so PauseAfterErrCount threshold is never reached. Also, zeroed keys are never pruned, leaking memory.

Why

Records that should be paused after N errors retry forever — the safety net silently doesn't work.

Fix

Key only on labels (processName + runID) which are stable. Use delete() instead of zeroing on Clear().

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Refactor
    • Error-counting interface now requires a primary stable label plus optional extras; keys are built consistently and clearing removes entries entirely.
  • Tests
    • Tests adjusted and expanded to validate new label-keying, independent counters per label set, explicit clear/remove behaviour, and pause/timeout scenarios driven by process/run identifiers rather than error instances.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 10, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0bd33021-56d0-40c6-beb7-fb6fbe3fa188

📥 Commits

Reviewing files that changed from the base of the PR and between 01c6cd0 and cfce9c0.

📒 Files selected for processing (2)
  • internal/errorcounter/errorcounter.go
  • internal/errorcounter/errorcounter_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/errorcounter/errorcounter_test.go

📝 Walkthrough

Walkthrough

The ErrorCounter public API was changed: Add, Count and Clear now take a required label string and optional extras ...string instead of an error plus variadic labels. Internal Counter logic now builds keys via makeKey(label, extras) (no error message included). Add increments using the new key, Count returns the keyed count, and Clear deletes the key entry. Tests and call sites (pause logic and several internal tests) were updated to use the new label-based arguments.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

  • luno/workflow#159: Modifies the ErrorCounter API/implementation with signature and keying changes closely matching this refactor.

Poem

🐇 I nibble labels, one by one,
Extras tucked where shadows run,
Keys stitched neat with tiny thread,
Counters climb and then reset,
A happy rabbit counts its fun 🎋

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: fixing error counter keying to use stable labels instead of dynamic error messages.
Description check ✅ Passed The description clearly explains the problem, impact, and solution, all of which are directly related to the changeset modifications.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/error-counter-key-stability

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

andrewwormald and others added 2 commits March 10, 2026 10:04
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The previous approach silently dropped the error message from the key
but kept labels optional. This meant callers without labels would share
a single global counter — a worse behaviour change.

Now the interface requires at least one label (label string, extras ...string),
making the contract explicit. All existing callers already pass two labels
(processName, runID) so no call sites change.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai coderabbitai Bot requested review from ScaleneZA and echarrod March 10, 2026 16:46
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/errorcounter/errorcounter.go`:
- Around line 47-51: The makeKey function currently joins label and extras with
"-" which can produce ambiguous keys (e.g., ("process-a","run-1") vs
("process","a","run-1")); update makeKey to encode segment boundaries
unambiguously (for example by prefixing lengths or using a separator-and-escape
scheme or null-byte delimiter) so each segment is recoverable and collisions are
impossible, and ensure any code that relies on makeKey (e.g., Clear or lookup
paths) continues to use the new encoding; also add a regression test that
constructs two colliding tuples like ("process-a","run-1") and
("process","a","run-1") and verifies they map to distinct keys and that Clear
only removes the intended entry.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b794c086-32b3-40b3-92b1-cc3fc50473bf

📥 Commits

Reviewing files that changed from the base of the PR and between f4d926f and 33333bc.

📒 Files selected for processing (3)
  • errors.go
  • internal/errorcounter/errorcounter.go
  • internal/errorcounter/errorcounter_test.go

Comment thread internal/errorcounter/errorcounter.go Outdated
The error value was accepted but ignored for keying — drop it entirely
to make the label-only keying explicit in the API.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
internal/errorcounter/errorcounter.go (1)

44-48: ⚠️ Potential issue | 🟠 Major

Use unambiguous key encoding; current join can collide.

At Line 48, label + "-" + strings.Join(extras, "-") can map different label tuples to the same key (for example, ("a-b","c") vs ("a","b-c")). That can merge counters and clear the wrong entry.

Proposed fix
 import (
+	"strconv"
 	"strings"
 	"sync"
 )
@@
 func makeKey(label string, extras []string) string {
-	if len(extras) == 0 {
-		return label
-	}
-	return label + "-" + strings.Join(extras, "-")
+	parts := make([]string, 0, len(extras)+1)
+	parts = append(parts, label)
+	parts = append(parts, extras...)
+
+	var b strings.Builder
+	for _, part := range parts {
+		b.WriteString(strconv.Itoa(len(part)))
+		b.WriteByte(':')
+		b.WriteString(part)
+		b.WriteByte('|')
+	}
+	return b.String()
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/errorcounter/errorcounter.go` around lines 44 - 48, The makeKey
function currently concatenates label and extras with "-" which allows
collisions (e.g. ("a-b","c") vs ("a","b-c")); change makeKey to produce an
unambiguous encoding by encoding the tuple instead of naive joining — for
example, serialize a small struct {Label string; Extras []string} (or otherwise
encode lengths/escape separators) using a stable encoder (e.g., json.Marshal)
and return that string; update makeKey to use that encoder so keys are unique
and reversible.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@internal/errorcounter/errorcounter.go`:
- Around line 44-48: The makeKey function currently concatenates label and
extras with "-" which allows collisions (e.g. ("a-b","c") vs ("a","b-c"));
change makeKey to produce an unambiguous encoding by encoding the tuple instead
of naive joining — for example, serialize a small struct {Label string; Extras
[]string} (or otherwise encode lengths/escape separators) using a stable encoder
(e.g., json.Marshal) and return that string; update makeKey to use that encoder
so keys are unique and reversible.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6a9a1332-4e22-428e-80b7-da5047503d50

📥 Commits

Reviewing files that changed from the base of the PR and between 33333bc and 01c6cd0.

📒 Files selected for processing (7)
  • errors.go
  • internal/errorcounter/errorcounter.go
  • internal/errorcounter/errorcounter_test.go
  • pause.go
  • pause_internal_test.go
  • step_internal_test.go
  • timeout_internal_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • errors.go

Labels containing hyphens (e.g. "a-b","c" vs "a","b-c") could map to
the same key. Use \x00 as the separator since it cannot appear in
human-readable label strings.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@andrewwormald andrewwormald enabled auto-merge (squash) May 13, 2026 19:01
@sonarqubecloud
Copy link
Copy Markdown

}

func (c *Counter) Add(err error, labels ...string) int {
func (c *Counter) Add(label string, extras ...string) int {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is technically a breaking change, but because we're pre v1.0.0, it's fine to release in a minor 👍 just worth noting in the release notes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants