Skip to content

Reuse historical incidents during issue grouping - #444

Open
arseniycodes wants to merge 7 commits into
mainfrom
ash/reuse-historical-incidents
Open

Reuse historical incidents during issue grouping#444
arseniycodes wants to merge 7 commits into
mainfrom
ash/reuse-historical-incidents

Conversation

@arseniycodes

@arseniycodes arseniycodes commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Summary

  • include resolved incidents in normal issue-grouping candidates
  • reopen the matched incident atomically when grouping selects resolved history
  • reuse the same incident for known issue recurrences without starting or steering agent work
  • retry mechanical grouping failures instead of creating standalone incidents
  • normalize numeric measurements in stackless log fingerprints

Verification

  • pnpm --filter @superlog/worker run test:incident-intake
  • pnpm --filter @superlog/worker run test:issue-routing
  • pnpm --filter @superlog/worker exec tsx --test src/issue-transitions.test.ts
  • pnpm --filter @superlog/worker typecheck
  • pnpm --filter @superlog/db exec tsx --test src/issue-lifecycle.test.ts
  • pnpm --filter @superlog/db typecheck
  • pnpm --filter @superlog/fingerprint test
  • pnpm worktree:verify
  • Biome check on changed files

Summary by cubic

Reuses historical incidents during grouping and on recurrences to keep one stable incident per root cause and avoid duplicate agent work. Adds a minute-level recovery sweep for stale grouping and commits grouping verdicts atomically with incident links, plus recovery of missing initial investigations after crashes.

  • New Features

    • Grouping searches open and resolved incidents; selecting a resolved one reopens and links it atomically, persisting the grouping verdict with the link. Candidate queries reserve capacity for open incidents so resolved history can’t evict active roots.
    • Known recurrences reopen their own incident before sibling grouping, preserving identity and avoiding new epochs.
    • Recovery sweep runs every minute, retries stale ungrouped issues after a short delay, continues past individual failures, and reports recovered counts via the superlog.incident_grouping_recovery.recovered_issues metric.
  • Bug Fixes

    • Prevent duplicate investigations/PRs: grouped issues trigger no agent work; if an investigation is active, we only append the linked issue as context.
    • Recover initial investigations: if a standalone incident exists with no agent run, the next intake queues its first investigation.
    • LLM grouping failures mark the issue as failed and throw IssueGroupingFailedError; the worker rethrows so pg-boss retries instead of creating standalone incidents.
    • Normalize numeric measurements in stackless log fingerprints (e.g., “1015ms”, “2.3s”, “kb/mb/gb”) to reduce fingerprint churn.

Written for commit 5df6ff9. Summary will update on new commits.

Review in cubic

@arseniycodes

Copy link
Copy Markdown
Contributor Author

@codex review

@superlog-app superlog-app Bot left a comment

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.

Observability review

  • 1 blocking
  • 1 warning

if (err instanceof IssueGroupingFailedError) {
logger.warn(
{
scope: "issue-transitions",

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.

logs · blocking — Log grouping retry at error, not warn

A grouping failure blocks issue intake and consumes pg-boss retry budget until it either succeeds or exhausts retries; operators must be able to alert on it. Replace logger.warn with logger.error so the structured log matches the severity of the failure.

Suggested change
scope: "issue-transitions",
logger.error(
{
scope: "issue-transitions",
issueId: data.issueId,
transition: data.transition,
projectId: data.projectId,
err: err.message,
},
"issue grouping failed; retrying transition",
);

Useful? React with 👍 / 👎.

eq(schema.incidentEvents.kind, AGENT_PULL_REQUEST_BATCH_RESERVATION_KIND),
isNull(schema.incidentEvents.processedAt),
),
);

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.

metrics · warning — Add a counter for resolved-incident reopens via grouping

The new joinIssueToIncident path can reopen a resolved incident, but no metric tracks how often this happens; without it operators cannot detect runaway reopen churn or validate that the feature is exercised in production. Increment a low-cardinality counter (e.g. superlog.incidents.reopened, dimension reason=issue_regressed) when previousStatus is non-null.

Useful? React with 👍 / 👎.

@arseniycodes

Copy link
Copy Markdown
Contributor Author

@codex review

@superlog-app superlog-app Bot left a comment

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.

Observability review

  • 1 blocking

if (err instanceof IssueGroupingFailedError) {
logger.warn(
{
scope: "issue-transitions",

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.

logs · blocking — Log grouping-failure retries at error, not warn

This path fires when intake cannot group an issue and pg-boss will retry the job; it is an operator-actionable failure (LLM/budget outage blocking incident creation), so it must be logger.error. Using warn here means alerting rules keyed on error severity will miss a sustained grouping outage.

Suggested change
scope: "issue-transitions",
logger.error(
{
scope: "issue-transitions",
issueId: data.issueId,
transition: data.transition,
projectId: data.projectId,
err: err.message,
},
"issue grouping failed; retrying transition",
);

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1b03cc6bb6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/worker/src/issues/repository.ts Outdated
where: and(
eq(schema.incidents.projectId, issue.projectId),
eq(schema.incidents.status, "open"),
inArray(schema.incidents.status, ["open", "resolved"]),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Reserve candidate capacity for open incidents

Including resolved rows in the same last-seen-ordered, capped candidate query can hide valid open join targets: when a project has 200 newer resolved incidents, an older open incident is omitted from every heuristic and LLM grouping pass. A new symptom belonging to that open incident is then classified as standalone, creating a duplicate incident and investigation; prioritize all open incidents or apply a separate limit to resolved history.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in b41f98f: open and resolved incidents now have independent candidate limits, with an integration test proving resolved history cannot evict an open incident.

@arseniycodes

Copy link
Copy Markdown
Contributor Author

@codex review

@superlog-app superlog-app Bot left a comment

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.

Observability review

  • 1 blocking

logger.warn(
{
scope: "issue-transitions",
issueId: data.issueId,

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.

logs · blocking — Log grouping retry at error, not warn

A warn here is invisible to alert rules watching for error-level events, yet this path means an issue is stuck in failed state and pg-boss is retrying—an operator must investigate if retries are exhausted. Use logger.error so the structured log is actionable.

Suggested change
issueId: data.issueId,
logger.error(
{
scope: "issue-transitions",
issueId: data.issueId,
transition: data.transition,
projectId: data.projectId,
err: err.message,
},
"issue grouping failed; retrying transition",
);

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b41f98f7d8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

): Promise<void> {
if (!grouping.failedReason) return;
await markIssueGrouping(issueId, grouping, repo);
throw new IssueGroupingFailedError(grouping.failedReason);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve retries when grouping runs inline

When pg-boss is unavailable or send fails, createIssueTransitionDispatcher runs this handler inline, so this exception has no queue retry path. Telemetry's flushIssueGroups catches it after the Issue was inserted and treats it as a row failure; later occurrences of that open fingerprint compute a seen transition and never invoke intake again. An LLM failure in this degraded mode therefore leaves the Issue permanently without an Incident, so retain a durable retry or avoid throwing when the transition is running inline.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in aadcb47. Failed or abandoned grouping attempts now remain durable in Postgres, and a minute-scheduled recovery job retries stale unlinked pending or failed issues in isolated batches. The existing queue path still throws for its native retry, while inline fallback failures are recovered by reconciliation.

Comment thread apps/worker/src/incidents/workflow.ts Outdated
Comment on lines +253 to +254
const routing = decideIssueArrivalRouting({ shouldInvestigate });
if (routing === "none") return intakeResult;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep active runs informed of newly linked issues

When a new signature groups into an Incident with an active agent run, shouldInvestigate is false and this return bypasses the removed appendContextChangeEvent path. The active run's existing issue bundle therefore lacks the new Issue ID, while validateCompleteIncidentIssueOutcomes rejects terminal resolution unless every current linked Issue has an outcome; symptom bursts can consequently prevent an otherwise successful investigation from resolving. Continue appending context to an active run without starting a new investigation.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in aadcb47. A newly linked issue now adds a deduplicated context-change event to an existing active run. This path never calls the investigation queue and does nothing when no run is already active.

@arseniycodes

Copy link
Copy Markdown
Contributor Author

@codex review

@superlog-app superlog-app Bot left a comment

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.

Observability review

  • 1 blocking
  • 1 warning

if (err instanceof IssueGroupingFailedError) {
logger.warn(
{
scope: "issue-transitions",

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.

logs · blocking — Use error severity for the grouping-failure rethrow log

This path fires when grouping fails and the job is about to be rejected for pg-boss retry; if retries are exhausted the issue transition is permanently lost, which operators must be able to alert on. warn is invisible to most alert rules and violates the rubric for operations that cannot complete.

Suggested change
scope: "issue-transitions",
logger.error(
{
scope: "issue-transitions",
issueId: data.issueId,
transition: data.transition,
projectId: data.projectId,
err: err.message,
},
"issue grouping failed; retrying transition",
);

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 4aa1801. Grouping failures that reject a queued transition are now logged at error severity.

}
await runIssueGroupingRecoverySweep({
now: () => new Date(),
listCandidates: (cutoff) =>

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.

metrics · warning — Record a counter for recovery sweep outcomes

The sweep returns a retriedCount that is silently discarded; without a metric, operators cannot detect a sweep that consistently finds zero candidates (misconfigured cutoff) or one that is retrying the same issues repeatedly (stuck queue). Add a low-cardinality counter at module scope and record it with the retried count after runIssueGroupingRecoverySweep resolves.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 4aa1801. Each recovery sweep now records the successfully recovered issue count in a low-cardinality counter, with a focused adapter test.

@arseniycodes

Copy link
Copy Markdown
Contributor Author

@codex review

@superlog-app superlog-app Bot left a comment

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.

Observability review

  • 2 warnings

scope: "incident-grouping-recovery",
issue_id: issue.id,
err: err instanceof Error ? err.message : String(err),
},

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.

logs · warning — Log the full error object, not just its message, in the recovery sweep

Replace err: err instanceof Error ? err.message : String(err) with err so the structured logger serialises the stack trace and error type; without them an operator cannot distinguish a transient network failure from a permanent schema mismatch when triaging stale-issue recovery failures.

Suggested change
},
deps.logger.error(
{
scope: "incident-grouping-recovery",
issue_id: issue.id,
err,
},
"failed to recover ungrouped issue",
);

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Also addressed in 3074858: recovery sweep failures now pass the full error object to the structured logger.

eq(schema.incidentEvents.kind, AGENT_PULL_REQUEST_BATCH_RESERVATION_KIND),
isNull(schema.incidentEvents.processedAt),
),
);

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.

metrics · warning — Increment a counter when joinIssueToIncident reopens a resolved incident

Add a superlog.incidents.reopened_total counter increment (with reason: "issue_regressed") inside the if (result.previousStatus) branch so operators can alert on unexpected reopen spikes and distinguish them from new-incident creation in the createdIncident metric.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4aa18016ab

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/worker/src/incidents/intake.ts Outdated
incident: freshIncident,
createdIncident: false,
linkedIssue: false,
shouldInvestigate: false,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve investigation queueing on intake replays

When a worker is redelivered after the first attempt committed the standalone Incident/link but died before queueAgentRunIfNeeded, this existing-link branch now returns shouldInvestigate: false. The retry therefore skips investigation queueing, and later occurrences of the already-open fingerprint do not re-enter intake, leaving the newly created Incident permanently without an agent run; determine whether the linked Incident already has a run rather than treating every existing link as proof that investigation was started.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 3074858. Intake replays now recover queueing only when the durable grouping verdict is standalone and the open Incident has no AgentRun. A grouped historical Incident remains no-investigation even when it has no run, preserving the reuse-only behavior.

@arseniycodes

Copy link
Copy Markdown
Contributor Author

@codex review

@superlog-app superlog-app Bot left a comment

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.

Observability review

  • 1 blocking
  • 1 warning

{
scope: "incident-grouping-recovery",
issue_id: issue.id,
err,

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.

metrics · warning — Add a failure counter for individual recovery-sweep retry errors

Increment a module-scope counter (e.g. superlog.incident_grouping_recovery.retry_failures) with dimension { scope: 'incident-grouping-recovery' } inside the catch block so operators can alert on a persistently broken sweep without parsing log lines. The recovered-issues counter alone cannot distinguish a sweep that retried 0 issues from one that retried 100 and failed all of them.

Suggested change
err,
deps.logger.error(
{
scope: "incident-grouping-recovery",
issue_id: issue.id,
err,
},
"failed to recover ungrouped issue",
);
// increment via injected dep so the pure sweep stays testable
deps.recordRetryFailure?.();

Useful? React with 👍 / 👎.

eq(schema.incidentEvents.kind, AGENT_PULL_REQUEST_BATCH_RESERVATION_KIND),
isNull(schema.incidentEvents.processedAt),
),
);

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.

logs · blocking — Log at info when joinIssueToIncident reopens a resolved incident

Add a structured info log (with incident_id, issue_id, and previous_status) immediately after the emitIncidentReopened call so operators can reconstruct which grouping decision caused a resolved incident to reappear without querying the database. The incident-reopened event is written to the incidentEvents table but is not surfaced in any log stream.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3074858a32

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +203 to +205
const groupingState = await repo.findIssueGroupingState(issueId);
if (groupingState !== "standalone") return false;
return !(await repo.hasAgentRunForIncident(incident.id));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Recover links committed before the standalone verdict

When standalone intake commits linkIssueToIncident at line 513 but the process dies—or updateIssueGrouping fails—before line 514, the durable state remains pending for the LLM path or the schema default grouped for a no-candidate path. On redelivery, the existing-link branch reaches this helper, which returns false because the state is not yet standalone, so the initial investigation is never queued; findStaleUngroupedIssues cannot repair it because it explicitly excludes issues that already have a link. Fresh evidence beyond the earlier replay fix is this still-non-atomic link/verdict ordering, so the verdict must commit with the link or replay must recognize linked, no-run standalone attempts without relying solely on the finalized state.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed at the transaction boundary in 5df6ff9. The grouping verdict is now persisted by joinIssueToIncident in the same database transaction as the link, so a link can no longer commit with a pending or default verdict. The database lifecycle test asserts the link and verdict together, and intake tests assert the atomic grouping payload.

@arseniycodes

Copy link
Copy Markdown
Contributor Author

@codex review

@superlog-app superlog-app Bot left a comment

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.

Observability review

  • 2 warnings

previousStatus,
};
});
if (result.previousStatus) {

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.

logs · warning — Log when joinIssueToIncident reopens a resolved Incident

Add an info log (with incidentId, issueId, and previousStatus) immediately after the reopen patch is applied inside the transaction so operators can correlate which issue triggered a resolved incident's reopen without a DB query. Without it, a sudden spike in reopened incidents is invisible until someone inspects the incident_reopened event rows directly.

Useful? React with 👍 / 👎.

const cutoff = new Date(deps.now().getTime() - ISSUE_GROUPING_RETRY_DELAY_MS);
const candidates = await deps.listCandidates(cutoff);
let retriedCount = 0;

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.

metrics · warning — Record total candidates found alongside recovered count in the sweep

Add a gauge or counter for candidates.length before the retry loop so operators can distinguish an idle sweep (zero candidates) from a sweep that found issues but failed all retries; the existing recovered_issues counter alone cannot surface that difference.

Suggested change
const cutoff = new Date(deps.now().getTime() - ISSUE_GROUPING_RETRY_DELAY_MS);
const candidates = await deps.listCandidates(cutoff);
if (deps.recordCandidateCount) deps.recordCandidateCount(candidates.length);
let retriedCount = 0;

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5df6ff9de6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +292 to 295
const routing = decideIssueArrivalRouting({ shouldInvestigate });
if (routing === "none") {
await appendIssueToActiveInvestigation(incident, issue, linkedIssue);
return intakeResult;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Notify Slack when a resolved incident is reopened

When a known resolved issue recurs, or a new issue groups into resolved history, intake returns createdIncident: false and shouldInvestigate: false; this branch consequently only attempts to append context to an active run. A resolved incident normally has no active run, so the reopen produces no Slack message at all, whereas the previous recurrence flow posted a new incident root. Projects relying on Slack therefore silently miss renewed production errors; post a reopen/thread notification independently of whether an investigation is active.

Useful? React with 👍 / 👎.

Comment on lines 47 to +50
"New issue to classify:",
JSON.stringify(input.newIssue, null, 2),
"",
`Open incident candidates (${input.candidates.length} total, services: ${services.join(", ") || "-"}, environments: ${environments.join(", ") || "-"}), one per line:`,
`Incident candidates (${input.candidates.length} total, including open and resolved history; services: ${services.join(", ") || "-"}, environments: ${environments.join(", ") || "-"}), one per line:`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Label candidate lifecycle status before asking the model

The prompt now says candidates include open and resolved history, but neither this index line nor GroupingCandidateIncident identifies which rows are open. For stackless issues that bypass the frame heuristic, legacy data can contain an open recurrence and a resolved predecessor with nearly identical context; the model may select the resolved row, reopening it while leaving the current incident open and recreating the duplicate aggregates this change is intended to avoid. Include status in each candidate and instruct grouping to prefer the matching open incident.

Useful? React with 👍 / 👎.

Comment on lines +161 to +164
return message.replace(
/\b\d+(?:\.\d+)?\s?(?:nanoseconds?|microseconds?|milliseconds?|seconds?|minutes?|hours?|days?|bytes?|ns|µs|us|ms|kb|mb|gb|tb|s|m|h|d|b|%)(?![a-z])/gi,
"<n>",
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve measurement dimensions in fingerprints

Replacing every duration, byte size, and percentage with the same <n> token makes semantically different stackless logs collide. For example, otherwise identical limit exceeded: 5s and limit exceeded: 5GB messages now receive the same service/type/body fingerprint, whereas the previous generic normalization retained s versus gb; ingestion will merge distinct failures into one Issue. Normalize units into dimension-specific tokens such as <duration>, <bytes>, and <percent> while still collapsing different values and scales within each dimension.

Useful? React with 👍 / 👎.

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.

1 participant