Repository navigation
Conversation
An accepted send can carry hogFlowVersion, the version of the template's workflow Django graded the hash against. When the cached workflow is older, cdp-api reads it from the primary. When the primary is older too, it answers 503 workflow_version_behind and queues nothing. The version stays out of the idempotency fingerprint, so a retry graded after an upload still replays. Without the field, the send behaves as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The hogflow worker reads the job's workflow from the primary when its cached copy is older than the version the accepted send carries, and fails the job when the primary is older too, so the worker never sends older content than Django graded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Concurrent sends that found an older cached workflow each read the primary, and the fresh copy was never cached, so the next send read the replica and the primary again. Primary reads are now shared per workflow and the result goes into the cache. A later replica load keeps a newer cached version. A send that joins a read which cannot meet its graded version reads the primary once more. The worker logs a version-behind job with both versions and counts it on cdp_hogflow_worker_jobs_failed_total, and the cdp-api refusal carries code workflow_version_behind. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The workflow_version_behind sub-code stays in logs, the outcome label and the cdp-api-to-Django body. Django maps it onto its existing public 503. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Silthus
force-pushed
the
email-v2/09-route-idempotency-cdp
branch
from
October 31, 2026 08:41
1a43bdd to
38613ba
Compare
Silthus
force-pushed
the
email-v2/10c-template-version-on-send
branch
from
October 31, 2026 08:41
ae46ab4 to
be15c53
Compare
A queued send whose workflow is older than its graded version fails its job in the worker. It now also records `failed` with `workflow_unavailable` on the send's email status, as a run whose workflow is missing does, so the status API stops answering `queued` for an email that never goes out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 31, 2026
This was referenced Oct 31, 2026
…mplate-version-on-send # Conflicts: # nodejs/src/cdp/services/hogflows/hogflow-manager.service.test.ts # nodejs/src/cdp/services/hogflows/hogflow-manager.service.ts
…ry read for them Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Owner
Author
|
Moved to PostHog#127130. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of the code-first rebuild of the email send stack; new in the rebuild, with no closed PR to replace: a template send delivers the content Django graded (decision D3 on map #461).
Problem
send/email/right after an upload can deliver the previous content, while Django answered that the caller'stemplate.hashis current.Impact
Pays into Grow Workflows through billed
workflows_emailsactions per paying org: a developer who uploads a template and sends it at once gets the new content, so the first send from code is right. In scope: CDP email sends.Merge Danger
Warning
🔴 Merge risk: high · two-way door: a revert sends from the cached copy again, as today
state.send.hogFlowVersionis set, which only Django from feat(messaging): send the workflow version a template send by key was checked against #585 sets, behindworkflows-email-from-code.Changes
hogFlowVersion. When the cached copy is older, cdp-api and the hogflow worker read the workflow from the primary.503 delivery_unavailable, and puts the version lag indetailandwhy(feat(messaging): send the workflow version a template send by key was checked against #585).workflow_version_behindappears only in the body cdp-api sends to Django, in the metric label and in the log. No public answer carries it.workflow_version_behindcount. Its email status records the send as failed, as for a workflow that is gone. Versions only go up, so this state should not happen.reason: 'workflow_unavailable', so the counter covers every job the worker fails before running it.version. An upload that lands between the grade and the send delivers the newer content. It is never older.43333f8. It now builds on feat(cdp): deliver a code send's content on its own transactional email route #565 and feat(cdp): key a code send on the sender the caller chose #580 only. Master's fix(cdp): answer transactional email sends without waiting on kafka PostHog/posthog#125444 (answer before the Kafka flush) and fix(workflows): drop suppressed cc/bcc instead of skipping the email PostHog/posthog#118456 (email status in the worker) are under it.How did you test this code?
Test seams: cdp-api's accepted-send route over HTTP (
cdp-api.serial.test.ts),processInvocationson the hogflow worker, and the hogflow manager service for the read count.Test rationale, each run red first:
{error}shape of cdp-api's other 503s.Proofs at
5d66b71:typescript:check, eslint, oxlint and prettier pass. The changed test files pass: 295 parallel and 174 serial tests.64d8940, move nine outcomes, each named by the goal: the version floor in cdp-api and the worker, the refusal when the database is older, the graded version on the queued run, the shared primary reads, and the new failed-jobs count. No test that passes on base fails on HEAD. Evidence is saved withfleet proof.dj3of map Map: send code-managed email templates with one PostHog call #461 runs the live stack. Not run here.Proofs at the pre-rebase heads
2bdc74bandae46ab4Live runs saved with
fleet proof, curl into maildev. A lost reload is simulated in SQL: the workflow's version and subject change with no reload published.2bdc74b, Django #5854d76e98)replayed: true, one email in totalae46ab4, differs from2bdc74bonly by the 503 body. No case above reaches that 503.CI: the fork has no Depot runners, so those jobs never start, and no secret for the project-board app, so
flags-project-boardfails; the other GitHub-hosted checks pass. The lane check passes: against master the PR changes onlynodejs/(cross_lane=false).Release status
Docs update
None.
🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Agent: Claude Code, Claude Opus 5.5 (claude-opus-5-5)
srv2of map Map: send code-managed email templates with one PostHog call #461, for decision D3.node3on 2026-10-31.tdd,writing-tests,stacking-prs,modernize-pr,pr-description,writing-pr-descriptions.delivery_unavailablewith the version lag indetailandwhy; replaces a new publicworkflow_version_behindcodegetHogFlowby name, so a rename fails loudly, and the public warm-up is a follow-upnode3, review of 2026-10-31Involvement: one decision from Michael and one from the map's conductor; review round 1 (GPT-6.1 Sol, Opus fresh eyes) fixed, then final reads by GPT-6.1 Sol; after the restack, one GPT-6.1 Sol review (one status finding, fixed in
f9abed0) and a final read; a full two-family panel on 2026-11-01 (8 lanes: 1 fixed, 1 rejected, shadow diff owed and run) and a narrow round (fresh eyes: description fixes); pre-rebase live and cross-version proof kept above.🤖 Generated with Claude Code