feat(signals): add bulk state transition and constrain dismissal reason#65008
Conversation
|
MCP UI Apps size report
|
|
Size Change: 0 B Total Size: 64.5 MB ℹ️ View Unchanged
|
The inbox `set-state` MCP tool took a single report id and an unvalidated free `dismissal_reason`, so agents running the autonomous inbox-triage loop had to fire one call per report and could leak invented reason codes into the inbox UI as raw chips. - Add a `bulk-state` action (and `inbox-reports-bulk-set-state` MCP tool) that transitions up to 100 reports in one call, processing each id independently and returning a compact per-id result (transitioned / skipped / failed / not_found) plus per-outcome counts. The single and bulk paths share one transition helper. - Constrain `dismissal_reason` to the canonical inbox codes (`already_fixed`, `report_unclear`, `analysis_wrong`, `wontfix_intentional`, `wontfix_irrelevant`, `other`) mirrored from the inbox UI source of truth, so reasons render as labelled chips and invented values are rejected. Generated-By: PostHog Code Task-Id: ac82cacf-24f4-47fc-8ed6-e00e76e54520
- Access user.uuid via getattr so the AnonymousUser branch type-checks (mypy union-attr). - Pin the shared `state` enum to SignalReportStateEnum via ENUM_NAME_OVERRIDES: the new bulk serializer added a second component referencing the suppressed/potential `state` enum, which tipped drf-spectacular into a non-optimally-resolvable name collision under --fail-on-warn (mirrors the existing Experiment type/status precedent). - Use _BULK_STATE_OUTCOME_DETAIL for the not_found branch too, removing a duplicated detail string (per Greptile review). Generated-By: PostHog Code Task-Id: ac82cacf-24f4-47fc-8ed6-e00e76e54520
Regenerate the committed MCP tool-schema snapshots (not covered by the OpenAPI auto-commit) for the new inbox-reports-bulk-set-state tool and the updated inbox-reports-set-state description. Produced via `pnpm --filter=@posthog/mcp run test -u`. Generated-By: PostHog Code Task-Id: ac82cacf-24f4-47fc-8ed6-e00e76e54520
8decf40 to
18003bf
Compare
|
🎭 Playwright report · View test results →
These issues are not necessarily caused by your changes. |
…reasons PR #65008 turned `inbox-reports-set-state`'s `dismissal_reason` into a server-validated enum and shipped `inbox-reports-bulk-set-state`, but left this skill describing the pre-#65008 contract — so its worked example taught agents a `dismissal_reason` (`not_a_bug`) the API now rejects with 400. - Replace the rejected `not_a_bug` example and the "caller-owned code" description with the six canonical codes (`already_fixed`, `report_unclear`, `analysis_wrong`, `wontfix_intentional`, `wontfix_irrelevant`, `other`), noting `already_fixed` snoozes (pair with `state: "potential"`) and `other` + a note is the catch-all. - Document `inbox-reports-bulk-set-state` (1–100 ids, per-id results, partial-failure semantics) and drop the now-false "the only exposed write" claims. Doc-only; no backend changes. Addresses the follow-up signal on inbox report 019ee28f-6c30-7ae4-8a26-26ea14f291a4 that #65008 did not cover. Generated-By: PostHog Code Task-Id: 17c42180-9197-486b-8e9a-2fbeaaa1b3fc
Problem
The inbox
inbox-reports-set-stateMCP tool (and its backingstateaction) takes a single reportidand an unvalidated free-formdismissal_reason. That blocks two things in the autonomous inbox-triage loop that dogfoods it:dismissal_reasonwas a freeCharFieldwhose help text suggested ad-hoc examples (not_a_bug,wont_fix,duplicate) that don't match the canonical inbox UI codes. Agent-invented values round-trip into the inbox as raw, unlabelled chips.Both gaps are success-path (every call returns 200), so they're invisible to error-rate monitoring.
Surfaced by PostHog inbox report
019ee28f-6c30-7ae4-8a26-26ea14f291a4.Changes
bulk-stateaction andinbox-reports-bulk-set-stateMCP tool transition up to 100 reports in one call. Each id is processed independently, so the call returns 200 even on partial failure with a compact per-id result (transitioned/skipped/failed/not_found) plus per-outcome counts. The single and bulk paths share one_transition_report_statehelper, so they honour identical restore/snooze semantics and transition guards.bulk_stateis added to the suppressed-visible actions so a bulk restore (state='potential') can reach archived reports; everything stays team-scoped via the existing queryset, so another team's id comes back asnot_found, never touched.dismissal_reasonis now aChoiceFieldconstrained to the inbox UI's canonical codes (already_fixed,report_unclear,analysis_wrong,wontfix_intentional,wontfix_irrelevant,other), mirrored fromfrontend/src/scenes/inbox/utils/dismissalReasons.ts. Invented codes are now rejected, and the tool descriptions note thatalready_fixedis a snooze (state='potential') rather than a dismissal.The response
outcomefield is a documentedCharFieldrather than aChoiceFieldon purpose — aChoiceFieldnamedoutcomecollides with another product'sOutcomeEnumin the shared OpenAPI schema, and a server-generated response value needs no input validation.The frontend already does its own client-side bulk via
Promise.allSettled, so it's left untouched; the new endpoint primarily serves the MCP/agent workflow. Wiring the frontend onto the single bulk call is a reasonable follow-up.How did you test this code?
I'm an agent. This environment has no database, so I could not run the Django suite or the OpenAPI codegen locally — CI will run both (and auto-commits the regenerated OpenAPI/MCP artifacts). I verified
ruff check/ruff formatpass and the files byte-compile, and I checked for OpenAPI enum-name collisions by hand (only the resolvedoutcomecase).Added automated coverage in
products/signals/backend/test/test_signal_report_api.py:skippedwhile the rest go through,not_foundand are never mutated (IDOR boundary),dismissal_reasonis now rejected.Automatic notifications
🤖 Agent context
Autonomy: Human-driven (agent-assisted) — directed by Andy Maguire via an inbox report action.
Authored with Claude Code (PostHog Code). Fetched the inbox report and its contributing signal finding via the PostHog MCP inbox tools + the
signalsHogQL skill, then implemented the fix. Skills invoked:/improving-drf-endpoints(serializer/viewset audit — drove theChoiceFieldconstraint and the enum-collision avoidance) and/implementing-mcp-tools(tool YAML conventions).Key decisions:
detail=Falsebulk-stateendpoint rather than overloading the single-reportstateaction, keeping the single-report contract (200/409/400) intact while the bulk form degrades per-id.dismissal_reasona strict enum (vs. just fixing the misleading examples) since the frontend already sends only canonical codes, so this aligns the API with the UI without breaking it.CharFieldfor the responseoutcometo dodge anOutcomeEnumcollision in the shared OpenAPI schema instead of renaming the field or threadingENUM_NAME_OVERRIDESthrough another product's generated types.