Skip to content

fix(agents): decode camelCase API responses, draft the suggested action - #195

Merged
liamcervante merged 1 commit into
mainfrom
fix/agents-api-camelcase-response-decoding
Jul 28, 2026
Merged

fix(agents): decode camelCase API responses, draft the suggested action#195
liamcervante merged 1 commit into
mainfrom
fix/agents-api-camelcase-response-decoding

Conversation

@liamcervante

@liamcervante liamcervante commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Why

preview_fix has been returning 500 {"error":"failed to generate action config"} for FinOps tagging findings. Reproduced on the demo org:

$ infracost tasks preview-fix 5f352453-… --finding-id 3491a601-… --org appleaccount
Info: generating action: Agents API returned status 500: {"error":"failed to generate action config"}

Root cause is a wire-contract drift in our Agents client. The API serializes its domain types straight to JSON (c.json<FindingDetail>(finding)), and the Agents API renamed those types from snake_case. Our structs still declared snake_case tags, so every multi-word response field silently decoded to its zero value. Single-word fields (id, title, status, effort) kept working, which is why nothing looked obviously broken. Before this change:

$ infracost findings get a09a7a2c-… --json
"finding_id": "",  "event_type": "",  "occurred_at": ""
# and estimatedMonthlySavings / suggestedAction / actionDescription / createdAt absent entirely

That's how we get to the 500. Agents deliberately downgrades a tagging fix to create_ticket when the resources aren't Terraform-managed (taggingPrEligibility.ts), but suggestedAction never reached the caller — so preview-fix fell back to its hardcoded open_pr, and generate-action 500s for a task with no repo to name.

Anything added to the API since June has never worked in the CLI.

What changed

Wire contract

  • Retagged Finding / Task / Action / FindingEvent / FindingTaskEvent to camelCase. Request bodies moved too (taskIds, actionType, dismissedReason) — Agents dual-accepts both casings today, and its own comments call our snake_case a compat shim, so this is the safe window to migrate off it.
  • Response fields that are genuinely snake_case server-side stay put, with a comment saying why: action_id, and the report endpoint's learning_id / dismissed_action_ids / confirmed_action_ids, are hand-built route literals rather than serialized domain types, so #515 never touched them.
  • Dropped has_running_actions / integration_id (no server counterpart at all); added lifecycleState / remediationState / prUrl (part of the current contract).

Paging — findings list is offset-paginated (page / per_page{items, pagination}), so --cursor was a silent no-op pinning every caller to page one. Now --page, and the result carries page / total_findings / total_pages / next_page.

Action typepreview-fix now defaults to the task's own suggestedAction. Explicit --type still wins and skips the lookup; a failed lookup or a task with no suggestion falls back to open_pr; a manual suggestion errors with what to do instead. The --type flag defaulted to "open_pr" at the cobra layer, which would have defeated this. MCP tool descriptions updated to tell the agent to omit type.

One extra fix in the same pathcreate-fix discarded the {type, config} envelope's type and defaulted to open_pr, so piping a ticket draft straight through submitted it as a PR. It now honors the drafted type unless --type is given. This only started to matter once tagging tasks actually drafted tickets.

Verified against the live API

findings get   → orgId, agentName, taskTotal, lifecycleState, createdAt, triggerDetail,
                 suggestedAction: "create_ticket"        (all previously "")
findings list  → "Saving: $143.00/mo"                    (previously absent)
                 "Showing 3 of 16 findings — pass --page 2 to see the next page."
preview-fix    → 422 {"code":"not_connected","error":"Connect a project management
   (the 500)      integration to create tickets."}
preview-fix    → open_pr, repo_url https://github.com/infracost-demo/data-warehouse
   (PR-eligible tagging task — unchanged)

A TestClientGetFindingDecodesCamelCaseFields case pins the wire casing against a payload shaped like coast's FindingDetail, so this can't rot silently again.

The Agents API serializes its domain types straight to JSON, and coast#515
("chore: camelCase findings domain types") renamed those types from
snake_case. Our structs still declared snake_case tags, so every multi-word
response field silently decoded to its zero value: no savings, no
timestamps, no actionDescription, no suggestedAction. Single-word fields
(id, title, status, effort) kept working, which is why nothing looked
obviously broken.

The visible symptom was `preview_fix` returning a 500 for FinOps tagging
findings. Agents deliberately downgrades a tagging fix to `create_ticket`
when the resources aren't managed by Terraform, but suggestedAction never
reached the caller, so preview-fix fell back to its hardcoded `open_pr` —
and generate-action 500s for a task with no repo to name.

- Retag Finding / Task / Action / FindingEvent / FindingTaskEvent to
  camelCase, and move request bodies over too (Agents dual-accepts both
  casings today, and its own comments call our snake_case a compat shim).
  Response fields that are genuinely snake_case server-side stay put —
  action_id and the report endpoint's learning_id / *_action_ids are
  hand-built route literals, not serialized domain types.
- Drop has_running_actions / integration_id, which have no server
  counterpart, and add lifecycleState / remediationState / prUrl, which do.
- Fix findings-list paging: the endpoint is offset-paginated (page /
  per_page, returning {items, pagination}), so `--cursor` was a silent
  no-op that pinned every caller to the first page. It's now `--page`.
- Default `preview-fix` to the task's own suggestedAction. An explicit
  --type still wins and skips the lookup; a failed lookup or a task with no
  suggestion falls back to open_pr; a `manual` suggestion errors with what
  to do instead. The --type flag defaulted to "open_pr" at the cobra layer,
  which would have defeated this.
- Honor the drafted type in `create-fix`: the {type, config} envelope's type
  was discarded, so piping a ticket draft through created an open_pr with a
  ticket config. This started to matter once tagging tasks actually drafted
  tickets.

Pin the wire casing in a client test so this can't rot silently again.
@liamcervante
liamcervante requested a review from a team July 27, 2026 17:06
@liamcervante
liamcervante merged commit 7004976 into main Jul 28, 2026
5 checks passed
@liamcervante
liamcervante deleted the fix/agents-api-camelcase-response-decoding branch July 28, 2026 13:32
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