Skip to content

Fix error counter keying on dynamic error messages#182

Open
andrewwormald wants to merge 4 commits intomainfrom
fix/error-counter-key-stability
Open

Fix error counter keying on dynamic error messages#182
andrewwormald wants to merge 4 commits intomainfrom
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
    • Updated error-counting interface to require a primary stable label plus optional extras; key construction made consistent and clearing now removes entries entirely.
  • Tests
    • Adjusted and expanded tests to validate new label-keying, independent counters per label set, and clear/remove behaviour across pause and timeout scenarios.

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

coderabbitai bot commented Mar 10, 2026

📝 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

Poem

🐇 I nibble labels, one by one,

Extras hop beneath the sun,
Keys are tidy, errors free,
Clear the burrow, count to three,
A tiny rabbit's jubilee 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarises the main change: updating error counter keying to use stable labels instead of dynamic error messages.
Description check ✅ Passed The description clearly explains the problem (dynamic error content causing key uniqueness), the impact (safety net failure), and the fix (stable label keying and memory cleanup).

✏️ 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

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 on lines +47 to +51
func makeKey(label string, extras []string) string {
if len(extras) == 0 {
return label
}
return label + "-" + strings.Join(extras, "-")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use an unambiguous key encoding here.

Line 51 can collide across different label tuples, for example ("process-a", "run-1") and ("process", "a", "run-1") both become process-a-run-1. That will merge unrelated counters and let Clear remove the wrong entry, which undermines the safety net this PR is fixing. Please encode segment boundaries explicitly instead of "-"-joining labels, and add a regression test for one of these collision cases.

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 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.

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

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