Problem
A report whose PR was opened by a Discuss task shows no PR anywhere in the report detail UI — no "Open in GitHub" action, no PR conversation, no CI checks — even though the PR exists and the report is listed in the Pull requests tab.
Concrete case: report 019fb453-08d0-7a33-be83-45bdbb10d5b5 (inbox) has an open PR at PostHog/code#4027, opened by a task associated to it as (product=signals, type=discussion). That task's run carries output.pr_url = https://github.com/PostHog/code/pull/4027 and state.signal_report_id pointing back at the report. The API still returns implementation_pr_url: null.
The contradiction shows up inside a single response — this list call returns the report and a null PR url:
GET /api/projects/2/signal_reports/?has_implementation_pr=true&task_id=6694c9d5-…
→ count: 1
id: 019fb453-08d0-7a33-be83-45bdbb10d5b5
implementation_pr_url: null # ← matched the "has a PR" filter, but has no PR
Root cause: three definitions of "the report's PR"
| Path |
Scope |
Result here |
_implementation_pr_report_filter (views.py) — drives the Pull requests tab |
any associated task with a non-empty output.pr_url |
report included |
_annotate_implementation_pr_url (views.py) — SQL annotation |
any associated task (associated_task_runs_filter is explicitly unfiltered by (product, type)) |
would be set |
fetch_implementation_pr_state_for_reports (implementation_pr.py) — injected as implementation_pr_url_map |
only type=implementation |
null |
SignalReportSerializer.get_implementation_pr_url prefers the context map whenever it's present, and both list() and retrieve() always inject it. So the narrow definition always wins, and the SQL annotation is effectively dead code on the retrieve path.
The task-run vocabulary already treats discussion as a first-class relationship (TASK_RUN_TYPE_DISCUSSION, record_report_task), and output.pr_url is populated task-type-agnostically (the GitHub webhook matches on branch/repo). So the data is there — only this one lookup filters it out.
Knock-on effects
With implementation_pr_url null, everything gated on it silently degrades:
ReportDetail.tsx — no "Open in GitHub" primary action, no PrCommentsSection, no PrChecksSection. (The Files-changed tab survives here only because a commit artefact independently supplies repo + branch.)
close_implementation_pr_for_report — suppressing or snoozing the report will not close the PR, so it lingers open.
_compute_inbox_notification_state — returns has_implementation_task=False and pr_available=False, so notifications describe the report as having no work in flight.
ReportDetailActions, useReportRefund, and the has_pr inbox analytics property all read as "no PR".
Suggested fix
Make the three paths agree. Simplest is to widen fetch_implementation_pr_state_for_reports to match the other two — resolve the PR from any associated signals task run rather than only type=implementation (the non-empty pr_url filter already supplies the specificity, which is the reasoning associated_task_runs_filter documents). If some run types must stay excluded, encode that as one shared predicate used by all three call sites so the list filter and the serialized field can't disagree again.
Worth deciding deliberately as part of this: billing. _bridges_with_pr_run counts only SignalReportTask.relationship = implementation, and a discussion task never writes that gate row — so a Discuss-opened PR is currently never billable. That may well be the intended policy, but it should be an explicit decision rather than a side effect of the same narrow filter.
Acceptance criteria
- A report whose only PR-bearing task run is a
discussion run serializes a non-null implementation_pr_url (and a correct implementation_pr_merged) from both list and retrieve.
- No report can match
has_implementation_pr=true while serializing implementation_pr_url: null, or vice versa.
- Suppressing/snoozing such a report closes its PR.
- Regression test covering a
discussion-only association, alongside the existing test_implementation_pr_url_resolves_from_artefact_only_association.
Problem
A report whose PR was opened by a Discuss task shows no PR anywhere in the report detail UI — no "Open in GitHub" action, no PR conversation, no CI checks — even though the PR exists and the report is listed in the Pull requests tab.
Concrete case: report
019fb453-08d0-7a33-be83-45bdbb10d5b5(inbox) has an open PR at PostHog/code#4027, opened by a task associated to it as(product=signals, type=discussion). That task's run carriesoutput.pr_url = https://github.com/PostHog/code/pull/4027andstate.signal_report_idpointing back at the report. The API still returnsimplementation_pr_url: null.The contradiction shows up inside a single response — this list call returns the report and a null PR url:
Root cause: three definitions of "the report's PR"
_implementation_pr_report_filter(views.py) — drives the Pull requests taboutput.pr_url_annotate_implementation_pr_url(views.py) — SQL annotationassociated_task_runs_filteris explicitly unfiltered by(product, type))fetch_implementation_pr_state_for_reports(implementation_pr.py) — injected asimplementation_pr_url_maptype=implementationSignalReportSerializer.get_implementation_pr_urlprefers the context map whenever it's present, and bothlist()andretrieve()always inject it. So the narrow definition always wins, and the SQL annotation is effectively dead code on the retrieve path.The task-run vocabulary already treats
discussionas a first-class relationship (TASK_RUN_TYPE_DISCUSSION,record_report_task), andoutput.pr_urlis populated task-type-agnostically (the GitHub webhook matches on branch/repo). So the data is there — only this one lookup filters it out.Knock-on effects
With
implementation_pr_urlnull, everything gated on it silently degrades:ReportDetail.tsx— no "Open in GitHub" primary action, noPrCommentsSection, noPrChecksSection. (The Files-changed tab survives here only because acommitartefact independently supplies repo + branch.)close_implementation_pr_for_report— suppressing or snoozing the report will not close the PR, so it lingers open._compute_inbox_notification_state— returnshas_implementation_task=Falseandpr_available=False, so notifications describe the report as having no work in flight.ReportDetailActions,useReportRefund, and thehas_prinbox analytics property all read as "no PR".Suggested fix
Make the three paths agree. Simplest is to widen
fetch_implementation_pr_state_for_reportsto match the other two — resolve the PR from any associated signals task run rather than onlytype=implementation(the non-emptypr_urlfilter already supplies the specificity, which is the reasoningassociated_task_runs_filterdocuments). If some run types must stay excluded, encode that as one shared predicate used by all three call sites so the list filter and the serialized field can't disagree again.Worth deciding deliberately as part of this: billing.
_bridges_with_pr_runcounts onlySignalReportTask.relationship = implementation, and a discussion task never writes that gate row — so a Discuss-opened PR is currently never billable. That may well be the intended policy, but it should be an explicit decision rather than a side effect of the same narrow filter.Acceptance criteria
discussionrun serializes a non-nullimplementation_pr_url(and a correctimplementation_pr_merged) from bothlistandretrieve.has_implementation_pr=truewhile serializingimplementation_pr_url: null, or vice versa.discussion-only association, alongside the existingtest_implementation_pr_url_resolves_from_artefact_only_association.