Skip to content

feat(tasks): add task-centric activity feed endpoint - #72793

Merged
k11kirky merged 17 commits into
masterfrom
posthog-code/task-activity-feed
Jul 27, 2026
Merged

feat(tasks): add task-centric activity feed endpoint#72793
k11kirky merged 17 commits into
masterfrom
posthog-code/task-activity-feed

Conversation

@raquelmsmith

@raquelmsmith raquelmsmith commented Jul 22, 2026

Copy link
Copy Markdown
Member

Problem

Activity in Channels needs to show task-level updates that require attention, including agent completion and permission requests. Read state must remain correct when new events arrive concurrently, and large feeds must not hide unread tasks behind a fixed first page.

Changes

Adds a task-centric activity API backed by a per-user, per-task projection.

  • Projects task creation, replies, mentions, awaiting-input events, agent responses, and completed turns.
  • Attributes agent activity to the agent while retaining the task creator as the activity recipient.
  • Keeps thread reads side-effect-free. The explicit mark_read action accepts per-task activity timestamps so an older client cannot clear newer activity.
  • Preserves read state when the same event is replayed and updates the projection only for newer activity.
  • Uses stable cursor pagination while reporting the unread count across the full feed.
  • Keeps successful thread posts successful if activity projection fails after the message is written.
  • Declares the request and response schemas used by generated frontend and MCP clients.

The companion desktop implementation is PostHog/code #3671.

How did you test this code?

Regression coverage includes:

  • stale read markers do not clear newer activity
  • replayed events do not become unread again
  • thread GET requests do not mutate read state
  • projection failures do not turn successful posts into failed requests
  • pagination traverses activity beyond the first page
  • agent responses and completed interactive turns create the expected activity

Validated with Ruff, formatting, repository-wide mypy, migration checks, OpenAPI and MCP generation, strict preflight, and the full backend CI matrix. Database-backed tests could not run in the agent workspace because Docker and Postgres were unavailable; they ran in CI.

Automatic notifications

  • Publish to changelog?
  • Alert Sales and Marketing teams?

Docs update

No standalone documentation change is needed. The generated OpenAPI and MCP contracts document the endpoint and request fields.

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

PostHog Code using Codex implemented and validated the final changes after a human review and manual product testing. Skills used: /django-migrations, /improving-drf-endpoints, /implementing-mcp-tools, /writing-tests, /writing-code-comments, and /running-ci-preflight.

The main design decisions were to keep reads side-effect-free, version explicit read mutations with the activity being acknowledged, and project completion independently of mobile push delivery. The branch was updated from master with a signed merge after an unrelated MCP integration failure.


Created with PostHog Code

k11kirky added 2 commits July 26, 2026 06:47
Adds `GET /api/projects/{team}/task_activity/`, a one-row-per-task feed of every
task the requester is involved in — created, @-mentioned in, or authored a thread
message on — ordered by most recent relevant activity. This backs the Channels
(project-bluebird) Activity view, which previously showed only raw @-mentions.

Each row's activity_kind names the winning signal (awaiting_input / message /
mention / created). "Awaiting your input" is derived from the durable
event="turn_complete" thread message, which only exists for channel-filed tasks;
a newer reply the user authored outranks an older turn-complete so the row reads
"message" instead. Aggregation is a fixed set of grouped queries gated through
`_visible_task_qs`, plus two supporting indexes on TaskThreadMessage.

Why: users want the Activity view to surface all activity involving them —
especially tasks an agent has updated and is waiting on them for — not just
mentions.

Generated-By: PostHog Code
Task-Id: c10b01e4-4645-4c82-98b2-610b533f7f7c
Generated-By: PostHog Code
Task-Id: c4f5025f-8edf-40d4-a163-7174899045d1
@k11kirky
k11kirky force-pushed the posthog-code/task-activity-feed branch from 7e171cc to 3d36de5 Compare July 26, 2026 05:47
k11kirky added 2 commits July 26, 2026 06:48
The feed endpoint could not return a row: list_task_activity built a
TaskActivityDTO with an `id` the dataclass did not declare, so every request
raised TypeError. WizardCloudRunDTO had picked up the mirror-image problem — a
required `id` its only construction site never passes. Both are fixed and the
serializer field lists now line up with their dataclasses.

`awaiting_input` was read off an `event="turn_complete"` thread message, which
nothing writes any more (list_thread_messages already filters those rows out as
legacy). It is now projected from notify_task_run_awaiting_input, so every path
that decides a run is waiting — stream ingest, agent proxy callback, sandbox
relay — feeds the same row, independent of the mobile push flag and cooldown.

Read state is per task rather than a feed-wide sweep. mark_read takes task ids,
and loading a task's thread clears that task's row, so reaching a task from the
sidebar counts the same as clicking it in the Activity list. Activity a user
caused themselves (their own task, their own reply) lands already-read, so the
badge only counts things actually waiting on them.

Also: the projection upsert is a single statement with a newest-wins conflict
clause, replacing a get_or_create read-modify-write that could lose a row under
concurrent messages and could drag activity_at backwards on a retried write. The
superseded read-time aggregation is deleted. The migration is renumbered onto
current master, drops its backfill (the feed is forward-only), and adds a partial
index for the unread count.


Generated-By: PostHog Code
Task-Id: 35a47457-b411-4d4b-80a5-ad06e2e6b1e5
The repo-check IDOR sweep requires every team-scoped model to appear in the
semgrep rule set. Without this the new model fails the check and cancels the rest
of Backend CI before the Django suite runs.


Generated-By: PostHog Code
Task-Id: 35a47457-b411-4d4b-80a5-ad06e2e6b1e5
@github-actions

github-actions Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

🤖 CI report

Bundle size — 🟢 -112 B (-0.0%)

Uncompressed size of every built .js bundle, compared against the base branch.

Total: 64.45 MiB · 🟢 -112 B (-0.0%)

No file changed by more than 1000 B.

Posted automatically by build-bundle-size-report · uncompressed bytes from dist-report

Eager graph — within budget

How much code each root ships on the eager path — downloaded and parsed before the surface is interactive. Measured from the esbuild output chunks (post-tree-shake, static imports only); lazy import() / React.lazy chunks are not counted.

Root Eager (shipped) Δ vs base Budget
entry (logged-out pages, app bootstrap)
src/index.tsx
1.24 MiB · 22 files no change ███░░░░░░░ 27.6% of 4.51 MiB
authenticated shell (every logged-in page)
src/scenes/AuthenticatedShell.tsx
8.08 MiB · 3,014 files no change ████████░░ 83.2% of 9.71 MiB

🟢 node_modules/monaco-editor/ stays out of src/index.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/index.tsx
🟢 [object Object] stays out of src/index.tsx
🟢 [object Object] stays out of src/index.tsx
🟢 node_modules/monaco-editor/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx

Largest files eagerly shipped from src/index.tsx
Size File
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
24.6 KiB ../node_modules/.pnpm/buffer@6.0.3/node_modules/buffer/index.js
6.3 KiB ../node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react.production.min.js
4.5 KiB ../node_modules/.pnpm/@jspm+core@2.1.0/node_modules/@jspm/core/nodelibs/browser/process.js
3.9 KiB ../node_modules/.pnpm/scheduler@0.23.2/node_modules/scheduler/cjs/scheduler.production.min.js
1.4 KiB ../node_modules/.pnpm/base64-js@1.5.1/node_modules/base64-js/index.js
1.3 KiB src/RootErrorBoundary.tsx
912 B ../node_modules/.pnpm/ieee754@1.2.1/node_modules/ieee754/index.js
789 B src/scenes/ChunkLoadErrorBoundary.tsx
762 B src/index.tsx
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx
Size File
281.5 KiB ../node_modules/.pnpm/posthog-js@1.407.3/node_modules/posthog-js/dist/rrweb.js
267.7 KiB ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
236.0 KiB src/taxonomy/core-filter-definitions-by-group.json
226.4 KiB ../node_modules/.pnpm/posthog-js@1.407.3/node_modules/posthog-js/dist/module.js
154.3 KiB ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
106.2 KiB src/lib/api.ts
94.0 KiB ../packages/quill/packages/quill/dist/index.js
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
90.6 KiB ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js

Posted automatically by check-eager-graph · sizes are eager output bytes (shipped, post-tree-shake) from the esbuild metafile · part of #32479

Toolbar bundle — eager 2.18 MiB within budget

What the toolbar ships to customer pages, measured from the esbuild output (minified, post-tree-shake). The eager set is the entry plus everything statically imported from it — fetched before any feature runs; deferred chunks load lazily. The eager guardrail is 5.72 MiB. Each output file must also stay below 10 MB, where CloudFront stops compressing it. The module boundary is enforced separately by check-toolbar-graph.

Metric Size Δ vs base Budget
Eager (shipped)
entry + static imports
2.18 MiB · 17 files no change ████░░░░░░ 38.2% of 5.72 MiB
Deferred (lazy) 2.07 MiB · 33 files no change n/a — loads on demand
Loader dist/toolbar.js 1.1 KiB no change █░░░░░░░░░ 5.8% of 19.5 KiB
Largest eagerly-shipped chunks
Size File
716.9 KiB dist/toolbar/toolbar-app-PSC4FBCN.css
545.7 KiB dist/toolbar/chunk-chunk-RPF3UQEA.js
484.3 KiB dist/toolbar/chunk-chunk-33S7B7JC.js
133.6 KiB dist/toolbar/chunk-chunk-TVLD7Q5U.js
131.8 KiB dist/toolbar/chunk-chunk-T5KY5WYR.js
71.0 KiB dist/toolbar/toolbar-app-4IZ7WDYG.js
69.0 KiB dist/toolbar/chunk-chunk-27JL52RE.js
35.6 KiB dist/toolbar/chunk-chunk-BVRVRZS6.js
20.9 KiB dist/toolbar/chunk-chunk-Z72TCCZX.js
12.2 KiB dist/toolbar/chunk-chunk-PIK3PADE.js

Posted automatically by check-toolbar-size · sizes are toolbar output bytes (shipped, post-tree-shake) from the esbuild metafile

Dist folder size — 🔺 +15.3 KiB (+0.0%)

Total size of the built frontend/dist folder (all assets), compared against the base branch.

Total: 1355.81 MiB · 🔺 +15.3 KiB (+0.0%)

ℹ️ MCP UI apps size — 32 app(s), 17066.0 KB JS

Built size of each MCP UI app (main.js + styles.css).

App JS CSS
debug 599.5 KB 187.7 KB
action 457.8 KB 187.7 KB
action-list 564.3 KB 187.7 KB
cohort 456.8 KB 187.7 KB
cohort-list 563.3 KB 187.7 KB
email-template 456.6 KB 187.7 KB
error-details 472.4 KB 187.7 KB
error-issue 457.5 KB 187.7 KB
error-issue-list 564.2 KB 187.7 KB
experiment 561.5 KB 187.7 KB
experiment-list 565.1 KB 187.7 KB
experiment-results 563.2 KB 187.7 KB
feature-flag 567.1 KB 187.7 KB
feature-flag-list 570.9 KB 187.7 KB
feature-flag-testing 461.0 KB 187.7 KB
insight-actors 562.1 KB 187.7 KB
invite-email-preview 456.0 KB 187.7 KB
llm-costs 559.5 KB 187.7 KB
session-recording 458.6 KB 187.7 KB
session-summary 463.9 KB 187.7 KB
survey 458.4 KB 187.7 KB
survey-global-stats 562.2 KB 187.7 KB
survey-list 565.0 KB 187.7 KB
survey-stats 562.2 KB 187.7 KB
trace-span 457.2 KB 187.7 KB
trace-span-list 564.2 KB 187.7 KB
workflow 457.1 KB 187.7 KB
workflow-list 563.7 KB 187.7 KB
loops-review 461.2 KB 187.7 KB
query-results 745.8 KB 187.7 KB
render-ui 826.4 KB 187.7 KB
visual-review-snapshots 461.6 KB 187.7 KB
Playwright — all passed

All tests passed.

View test results →

⚠️ Backend coverage — 95.0% of changed backend lines covered — 18 uncovered

🧪 Backend test coverage

Patch coverage — changed backend lines (products + core): ███████████████████░ 95.0% (344 / 362)

File Patch Uncovered changed lines
products/tasks/backend/temporal/process_task/activities/relay_sandbox_events.py 25.0% 405, 760, 762
products/tasks/backend/push_dispatcher.py 77.8% 101–102, 112–113
products/tasks/backend/presentation/views/channels_api.py 83.9% 218, 221–224
products/tasks/backend/logic/stream/event_ingest.py 85.7% 422
products/tasks/backend/facade/api.py 91.3% 5260, 5295, 5313, 5347
products/tasks/backend/presentation/serializers.py 97.0% 1500

🤖 Agents: add a test covering the lines above, or note why under "How did you test this code?". Machine-readable gap list: the patch-coverage artifact on this run (gh run download 30269703496 -n patch-coverage), or the coverage-data block at the end of this comment.

Per-product line coverage (touched products)
Product Coverage Lines
platform_features ██░░░░░░░░░░░░░░░░░░ 12.1% 7 / 58
batch_exports ████████░░░░░░░░░░░░ 39.6% 8,414 / 21,261
demo ███████████░░░░░░░░░ 56.2% 1,497 / 2,663
warehouse_sources_queue ████████████░░░░░░░░ 59.2% 148 / 250
tasks ██████████████░░░░░░ 69.6% 30,976 / 44,536
data_tools ██████████████░░░░░░ 70.0% 63 / 90
ai_gateway ███████████████░░░░░ 75.0% 9 / 12
signals ████████████████░░░░ 80.5% 22,797 / 28,327
cdp ████████████████░░░░ 81.0% 3,146 / 3,883
data_modeling █████████████████░░░ 84.2% 6,549 / 7,781
wizard █████████████████░░░ 84.8% 935 / 1,102
notebooks █████████████████░░░ 85.6% 7,517 / 8,777
agent_platform █████████████████░░░ 86.4% 3,807 / 4,405
actions █████████████████░░░ 86.6% 717 / 828
cohorts ██████████████████░░ 87.8% 4,488 / 5,114
product_tours ██████████████████░░ 87.9% 1,303 / 1,482
data_warehouse ██████████████████░░ 88.3% 12,086 / 13,692
exports ██████████████████░░ 88.4% 6,950 / 7,862
business_knowledge ██████████████████░░ 89.0% 4,391 / 4,936
conversations ██████████████████░░ 89.3% 16,953 / 18,978
engineering_analytics ██████████████████░░ 89.4% 6,386 / 7,145
dashboards ██████████████████░░ 89.4% 5,983 / 6,693
visual_review ██████████████████░░ 89.5% 5,837 / 6,522
error_tracking ██████████████████░░ 89.7% 10,174 / 11,343
alerts ██████████████████░░ 90.0% 4,056 / 4,508
mcp_analytics ██████████████████░░ 90.2% 2,879 / 3,193
streamlit_apps ██████████████████░░ 90.4% 2,501 / 2,767
links ██████████████████░░ 90.6% 183 / 202
slack_app ██████████████████░░ 91.0% 9,075 / 9,978
marketing_analytics ██████████████████░░ 91.1% 11,903 / 13,072
stamphog ██████████████████░░ 91.1% 4,056 / 4,450
mcp_store ██████████████████░░ 91.9% 4,257 / 4,634
product_analytics ███████████████████░ 92.5% 5,848 / 6,319
notifications ███████████████████░ 92.6% 1,012 / 1,093
early_access_features ███████████████████░ 92.6% 1,287 / 1,390
ai_observability ███████████████████░ 92.8% 15,228 / 16,409
surveys ███████████████████░ 93.0% 5,769 / 6,201
managed_migrations ███████████████████░ 93.1% 1,429 / 1,535
web_analytics ███████████████████░ 93.2% 14,826 / 15,906
posthog_ai ███████████████████░ 93.2% 1,326 / 1,422
approvals ███████████████████░ 93.3% 3,437 / 3,682
reminders ███████████████████░ 93.4% 468 / 501
workflows ███████████████████░ 93.6% 6,545 / 6,992
legal_documents ███████████████████░ 94.1% 1,568 / 1,667
endpoints ███████████████████░ 94.1% 8,640 / 9,177
tracing ███████████████████░ 94.5% 2,670 / 2,826
messaging ███████████████████░ 94.5% 2,811 / 2,974
review_hog ███████████████████░ 94.6% 6,898 / 7,289
skills ███████████████████░ 94.6% 3,158 / 3,337
logs ███████████████████░ 95.4% 10,018 / 10,504
experiments ███████████████████░ 95.8% 25,365 / 26,485
growth ███████████████████░ 96.1% 3,245 / 3,376
annotations ███████████████████░ 96.2% 732 / 761
replay_vision ███████████████████░ 96.3% 15,595 / 16,199
revenue_analytics ███████████████████░ 96.3% 1,887 / 1,960
feature_flags ███████████████████░ 96.4% 17,372 / 18,025
user_interviews ███████████████████░ 96.5% 2,638 / 2,734
access_control ███████████████████░ 96.9% 870 / 898
warehouse_sources ███████████████████░ 97.2% 340,242 / 350,127
customer_analytics ███████████████████░ 97.2% 9,757 / 10,038
data_catalog ████████████████████ 97.6% 2,469 / 2,530
analytics_platform ████████████████████ 98.0% 2,153 / 2,197
metrics ████████████████████ 98.2% 2,491 / 2,536
pulse ████████████████████ 98.4% 2,017 / 2,049
live_debugger ████████████████████ 99.2% 613 / 618
field_notes ████████████████████ 99.4% 158 / 159

Report-only. Patch coverage = changed backend lines covered vs origin/master. Sorted lowest first.
Known gaps: lines covered only by Temporal tests show as uncovered; core line numbers may drift if master changed the same file.

⚠️ Django migration SQL — 1 new migration to review

We've detected new migrations on this PR. Review the SQL output for each migration:

products/tasks/backend/migrations/0073_task_activity.py

BEGIN;
--
-- Create model TaskActivity
--
CREATE TABLE "posthog_task_activity" ("id" uuid NOT NULL PRIMARY KEY, "kind" varchar(32) NOT NULL, "activity_at" timestamp with time zone NOT NULL, "read_at" timestamp with time zone NULL, "message_id" uuid NULL, "task_id" uuid NOT NULL, "team_id" integer NOT NULL, "user_id" integer NOT NULL);
--
-- Create constraint task_activity_user_task_unique on model taskactivity
--
ALTER TABLE "posthog_task_activity" ADD CONSTRAINT "task_activity_user_task_unique" UNIQUE ("team_id", "user_id", "task_id");
--
-- Create index task_activity_feed_idx on field(s) team, user, activity_at, id of model taskactivity
--
CREATE INDEX "task_activity_feed_idx" ON "posthog_task_activity" ("team_id", "user_id", "activity_at", "id");
--
-- Create index task_activity_unread_idx on field(s) team, user of model taskactivity
--
CREATE INDEX "task_activity_unread_idx" ON "posthog_task_activity" ("team_id", "user_id") WHERE "read_at" IS NULL;
ALTER TABLE "posthog_task_activity" ADD CONSTRAINT "posthog_task_activit_message_id_2d258364_fk_posthog_t" FOREIGN KEY ("message_id") REFERENCES "posthog_task_thread_message" ("id") DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE "posthog_task_activity" ADD CONSTRAINT "posthog_task_activity_task_id_c4d245b1_fk_posthog_task_id" FOREIGN KEY ("task_id") REFERENCES "posthog_task" ("id") DEFERRABLE INITIALLY DEFERRED;
CREATE INDEX "posthog_task_activity_message_id_2d258364" ON "posthog_task_activity" ("message_id");
CREATE INDEX "posthog_task_activity_task_id_c4d245b1" ON "posthog_task_activity" ("task_id");
CREATE INDEX "posthog_task_activity_team_id_605c66cc" ON "posthog_task_activity" ("team_id");
CREATE INDEX "posthog_task_activity_user_id_9c37f739" ON "posthog_task_activity" ("user_id");
COMMIT;

Last updated: 2026-07-27 13:22 UTC (c4abdd2)

Django migration risk — migration analysis complete

We've analyzed your migrations for potential risks.

Summary: 1 Safe | 0 Needs Review | 0 Blocked

✅ Safe

Brief or no lock, backwards compatible

tasks.0073_task_activity
  └─ #1 ✅ CreateModel
     Creating new table is safe
     model: TaskActivity
  │
  └──> ℹ️  INFO:
       ℹ️  Skipped operations on newly created tables (empty tables
       don't cause lock contention).

Last updated: 2026-07-27 13:22 UTC (c4abdd2)

tests-posthog Bot and others added 3 commits July 26, 2026 06:00
The generated client typed taskActivityList as returning a paginated envelope,
because drf-spectacular assumes the viewset's default pagination. The endpoint
sends one page object carrying its own unread total, so any consumer reading
`results` off the top level would have found the wrong shape. pagination_class =
None on the viewset.

Also switches the TaskActivity primary key to uuid7, which satisfies
prefer-uuid7-django-pk without a suppression. The sibling task models carry
nosemgrep waivers because they predate the rule; a new table has no such
constraint, and a time-ordered key suits this one — rows are insert-heavy and
read newest-first, so index appends stay local and the id becomes a meaningful
tiebreak when two rows share an activity_at.


Generated-By: PostHog Code
Task-Id: 35a47457-b411-4d4b-80a5-ad06e2e6b1e5
@k11kirky k11kirky added the run-ci-backend Force ci-backend's full test matrices to run even on a draft PR label Jul 26, 2026 — with PostHog
k11kirky and others added 3 commits July 26, 2026 08:14
Dropping pagination stopped drf-spectacular wrapping the response in a paginated
envelope, but its list-view heuristic still typed the operation as an array of
page objects. `list` returns exactly one envelope (results + unread_count), so
force the heuristic off with an AutoSchema subclass, following the same pattern
review_hog uses for its page envelope — including pinning the operationId back to
`*_list`, since disabling the heuristic otherwise renames it to `*_retrieve`.

Also records the task-activity post_save receiver in setup_receivers_baseline.txt.
test_setup_receivers_match_baseline fails on any receiver connecting during
django.setup() that isn't listed; this one lives in the same already-imported
module as track_task_run_completion, so it adds no startup import weight.


Generated-By: PostHog Code
Task-Id: 35a47457-b411-4d4b-80a5-ad06e2e6b1e5
Generated-By: PostHog Code
Task-Id: 35a47457-b411-4d4b-80a5-ad06e2e6b1e5
@github-actions

github-actions Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

🦔 Hogbox preview · ✅ ready

▶ Open the preview

🔑 Login test@posthog.com / 12345678 (demo data)
🧩 Running this PR's backend and frontend, on the PostHog :master base
🔗 Link stable across rebuilds — a re-push swaps the box underneath, the URL stays
🔒 Access tailnet only (PostHog VPN)
🛠️ Admin inspect & debug state in hogland
💤 Idle sleeps after ~30 min idle (snapshot to S3, zero node cost) and wakes on your next visit in ~30s, behind a brief "waking up" screen

commit c4abdd2 · box box-0b52b4e89356 · ready in 974s (push → usable) · build log · rebuilds on every push, torn down on close

@pr-assigner-resolver-posthog
pr-assigner-resolver-posthog Bot requested a review from a team July 26, 2026 09:10
@greptile-apps

greptile-apps Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
products/tasks/backend/facade/api.py:5268
**Replayed waits become fresh activity**

When an awaiting-input notification is replayed after the user reads the task or newer activity arrives, assigning `django_timezone.now()` makes the old event win the newest-wins upsert and resets `read_at` to null, causing a stale awaiting-input row and unread badge.

### Issue 2 of 2
products/tasks/backend/facade/api.py:5142
**Read state races message retrieval**

When another message lands between this update and the following message query, the response includes that message while its activity remains unread, causing the user to see the message while the sidebar still marks its task unread. A query failure after the committed update instead clears activity without returning the thread.

Reviews (1): Last reviewed commit: "chore(tasks): annotate the activity quer..." | Re-trigger Greptile

user_id=creator_id,
task_id=task_run.task_id,
kind=TaskActivity.Kind.AWAITING_INPUT,
activity_at=django_timezone.now(),

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.

P1 Replayed waits become fresh activity

When an awaiting-input notification is replayed after the user reads the task or newer activity arrives, assigning django_timezone.now() makes the old event win the newest-wins upsert and resets read_at to null, causing a stale awaiting-input row and unread badge.

Prompt To Fix With AI
This is a comment left during a code review.
Path: products/tasks/backend/facade/api.py
Line: 5268

Comment:
**Replayed waits become fresh activity**

When an awaiting-input notification is replayed after the user reads the task or newer activity arrives, assigning `django_timezone.now()` makes the old event win the newest-wins upsert and resets `read_at` to null, causing a stale awaiting-input row and unread badge.

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

Comment thread products/tasks/backend/facade/api.py Outdated
Generated-By: PostHog Code
Task-Id: 744f81ec-6b8e-420c-960c-541b1932b46c
Comment thread products/tasks/backend/facade/api.py Outdated
@veria-ai

veria-ai Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

PR overview

All previously flagged issues have been addressed. No open security concerns remain on this pull request.

Security review

No open security issues remain on this pull request.

Fixed/addressed: 1 · PR risk: 0/10

Generated-By: PostHog Code
Task-Id: 744f81ec-6b8e-420c-960c-541b1932b46c
Generated-By: PostHog Code
Task-Id: 744f81ec-6b8e-420c-960c-541b1932b46c
Generated-By: PostHog Code
Task-Id: 744f81ec-6b8e-420c-960c-541b1932b46c
Generated-By: PostHog Code
Task-Id: 744f81ec-6b8e-420c-960c-541b1932b46c
Comment on lines +81 to +84
def notify_task_run_turn_completed(task_run: TaskRun) -> None:
_project_completed_activity(task_run)
_enqueue(task_run, kind="awaiting", body=f'"{_task_title(task_run)}" finished')

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.

Inconsistency between activity projection and push notification kind. The function projects a COMPLETED activity via _project_completed_activity(task_run) but enqueues a push notification with kind="awaiting". This mismatch could cause:

  1. The activity feed showing "completed" while push notifications show "awaiting"
  2. Client code expecting these to match may break
  3. User confusion from inconsistent messaging

Fix:

def notify_task_run_turn_completed(task_run: TaskRun) -> None:
    _project_completed_activity(task_run)
    _enqueue(task_run, kind="completed", body=f'"{_task_title(task_run)}" finished')

Or if "awaiting" is intentional, the activity projection should use _project_awaiting_input_activity instead.

Suggested change
def notify_task_run_turn_completed(task_run: TaskRun) -> None:
_project_completed_activity(task_run)
_enqueue(task_run, kind="awaiting", body=f'"{_task_title(task_run)}" finished')
def notify_task_run_turn_completed(task_run: TaskRun) -> None:
_project_completed_activity(task_run)
_enqueue(task_run, kind="completed", body=f'"{_task_title(task_run)}" finished')

Spotted by Graphite

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

Generated-By: PostHog Code
Task-Id: 744f81ec-6b8e-420c-960c-541b1932b46c
Generated-By: PostHog Code
Task-Id: 744f81ec-6b8e-420c-960c-541b1932b46c
@k11kirky
k11kirky merged commit fe818cb into master Jul 27, 2026
257 checks passed
@k11kirky
k11kirky deleted the posthog-code/task-activity-feed branch July 27, 2026 13:58
@deployment-status-posthog

deployment-status-posthog Bot commented Jul 27, 2026

Copy link
Copy Markdown

Deploy status

Environment Status Deployed At Workflow
dev ✅ Deployed 2026-07-27 14:36 UTC Run
prod-us ✅ Deployed 2026-07-27 14:51 UTC Run
prod-eu ✅ Deployed 2026-07-27 14:52 UTC Run

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

run-ci-backend Force ci-backend's full test matrices to run even on a draft PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants