Skip to content

fix: resolve sequential incident references via filter[sequential_id]#161

Merged
spencerhcheng merged 2 commits into
mainfrom
fix/sequential-incident-lookup-400
Jul 23, 2026
Merged

fix: resolve sequential incident references via filter[sequential_id]#161
spencerhcheng merged 2 commits into
mainfrom
fix/sequential-incident-lookup-400

Conversation

@spencerhcheng

Copy link
Copy Markdown
Collaborator

Problem

get_incident (and every other tool that accepts an incident reference) fails with 400 Bad Request whenever a bare sequential incident number (e.g. 57597) is passed. UUIDs work fine — only the sequential-number path breaks. Reported from production; started failing in the last 1–2 weeks.

Observed:

400 Bad Request for
/v1/incidents?page[size]=100&page[number]=245&fields[incidents]=id,sequential_id&sort=-created_at

Cause

_resolve_incident_reference_to_uuid resolved a sequential number by walking the whole /v1/incidents list: it binary-searched pages sorted by -created_at to find the matching sequential_id. With a large incident history the first midpoint probe requests a high page[number] (e.g. 245 → offset 24,500), and the Rootly API rejects deep pagination with a 400. Because the first probe always lands deep, the lookup failed regardless of which number was passed. UUID lookups skip this path (GET /v1/incidents/{uuid}), which is why they kept working. It "worked until recently" because the account's incident volume crossed the API's pagination-depth limit.

Fix

Resolve the number with a single direct filter[sequential_id] request — the filter the API provides specifically for "the 123 in INC-123":

GET /v1/incidents?filter[sequential_id]=57597&page[size]=1&fields[incidents]=id,sequential_id
  • Removes the deep-pagination 400 entirely.
  • Cuts the resolver from up to log2(total_pages) requests to one.
  • Keeps a defensive exact-match check so an ignored/unsupported filter never resolves to the wrong incident.

This resolver backs get_incident, update_incident, list_incident_roles, find_related_incidents, suggest_solutions, and the incident resource, so all reference paths are fixed together.

Related hardening (found while auditing)

  • Case-insensitive INC- prefix — lowercase inc-57597 previously fell through to a literal /v1/incidents/inc-57597 path (404); LLM clients lowercase constantly.
  • suggest_solutions page[size] 150 → 100 — 150 exceeds the API maximum and can itself 400.

Tests

  • Updated resolver tests to expect the single filter[sequential_id] call.
  • Added a lowercase inc- case and an "ignored filter → not_found" guard test.
  • Switched three fixtures off the unrealistic inc-123 id (now correctly parsed as sequential) to a UUID.
ruff check   -> All checks passed!
pyright      -> 0 errors, 0 warnings
pytest tests/unit -> 507 passed

Note: verified against the API docs (filter[sequential_id]) and the full unit suite. A live check against a real deep account is worth doing before release — I couldn't run one here (no valid API token in the dev environment).

Resolving an incident by its human-readable number (57597, #57597,
INC-57597) walked the entire /v1/incidents list to find the matching
sequential_id. The resolver binary-searched pages sorted by -created_at,
and its first midpoint probe requested a high page[number] (e.g. 245).
The Rootly API rejects deep pagination with 400 Bad Request once an
account has enough incidents, so the first probe failed regardless of
the number passed — sequential lookups were effectively dead while UUID
lookups (which skip this path) kept working.

Replace the page-walk with a single direct filter[sequential_id] lookup,
which the API supports specifically for the "123 in INC-123". This
removes the deep-pagination failure entirely and cuts the resolver from
up to log2(total_pages) requests down to one. A defensive exact-match
check guards against the filter being ignored so we never resolve to the
wrong incident. This one resolver backs get_incident, update_incident,
list_incident_roles, find_related_incidents, suggest_solutions, and the
incident resource, so all reference paths are fixed together.

Also harden two related issues found while auditing:
- Make the INC- prefix case-insensitive (re.IGNORECASE) so lowercase
  inc-57597 resolves as sequential instead of falling through to a
  literal /v1/incidents/inc-57597 path (404).
- Cap suggest_solutions page[size] at the API maximum of 100 (was 150,
  which the API rejects).

Tests: updated resolver tests to expect the single filter call, added a
lowercase inc- case and an ignored-filter guard test, and switched three
fixtures off the unrealistic "inc-123" id (now parsed as sequential) to
a UUID. ruff + pyright clean; full unit suite (507) passes.
@greptile-apps

greptile-apps Bot commented Jul 22, 2026

Copy link
Copy Markdown

Greptile Summary

This PR replaces page-based incident-number resolution with a direct filtered lookup. The main changes are:

  • Adds a single filter[sequential_id] request with exact-match validation.
  • Accepts lowercase and mixed-case INC- prefixes.
  • Reduces solution-mining requests to the 100-item API page limit.
  • Updates resolver tests and release notes.

Confidence Score: 5/5

The change looks mergeable, but unsupported-filter behavior can incorrectly report existing incidents as missing.

  • The direct lookup removes the deep-pagination failure.
  • Exact-match validation prevents resolving an unrelated incident.
  • API deployments that ignore the new filter have no compatibility fallback.

src/rootly_mcp_server/tools/incidents.py

Important Files Changed

Filename Overview
src/rootly_mcp_server/tools/incidents.py Adds direct sequential-ID filtering and case-insensitive reference parsing, with a compatibility gap when the filter is unavailable or ignored.
tests/unit/test_tools.py Updates resolver expectations and covers lowercase references, empty results, and mismatched filtered responses.
CHANGELOG.md Documents the resolver, prefix handling, and page-size changes.
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
src/rootly_mcp_server/tools/incidents.py:134-136
**Unsupported Filter Causes False Not-Found**

If an API deployment ignores or does not support `filter[sequential_id]`, this one-item request returns an unrelated incident. The exact-match guard then raises `LookupError`, so every existing sequential reference is reported as not found with no fallback lookup.

Reviews (1): Last reviewed commit: "fix: resolve sequential incident referen..." | Re-trigger Greptile

Comment on lines +134 to +136
params={
"filter[sequential_id]": target_sequential_id,
"page[size]": 1,

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 Unsupported Filter Causes False Not-Found

If an API deployment ignores or does not support filter[sequential_id], this one-item request returns an unrelated incident. The exact-match guard then raises LookupError, so every existing sequential reference is reported as not found with no fallback lookup.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/rootly_mcp_server/tools/incidents.py
Line: 134-136

Comment:
**Unsupported Filter Causes False Not-Found**

If an API deployment ignores or does not support `filter[sequential_id]`, this one-item request returns an unrelated incident. The exact-match guard then raises `LookupError`, so every existing sequential reference is reported as not found with no fallback lookup.

How can I resolve this? If you propose a fix, please make it concise.

@spencerhcheng
spencerhcheng merged commit 646af22 into main Jul 23, 2026
7 checks passed
@spencerhcheng
spencerhcheng deleted the fix/sequential-incident-lookup-400 branch July 23, 2026 16:23
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