You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Ship code-managed email templates as one reviewed draft PR #498
Turn the integration branch spec/code-managed-templates into one reviewed draft PR on PostHog/posthog, per SHIP.md "Ship ticket". It covers Spec: code-managed email templates tickets #487–#492.
Integration branch spec/code-managed-templates is created on the fork from a5dbb10460b, the reviewed head of the #486 migration PR PostHog/posthog#118769. The ship PR stacks on that PR.
Runtime: T3 child task
Task: node:delegated-task:command%3Amcp%3A955e36b3-3986-469c-b816-09cf656780af%3Adelegate-task%3Awf-email-ship-498-1
Cast: Opus 5.5, high. Cap 300 minutes. Ships spec/code-managed-templates at 7845707ba33, stacked on PostHog#118769. CodeRabbit is retired in this repo, so no slot is held.
PR:PostHog/posthog#118924 (draft), branch Silthus:spec/code-managed-templates, stacked on #118769 (review starts after a5dbb10460b). Reviewed head:07d7e0ee544c7ea081a14d5d7001611b31a68e66. fleet proof gate exits 0 on it.
qa-swarm, because the diff touches auth and API scopes, the save-and-sync transaction and public API contracts.
Round
HEAD
Lanes
Items
Fixed
Follow-up
Rejected
1 (full)
e52feb3ccd9
GPT-6.1 Sol high: correctness, risk, design, systems, author perspective (review-hog-perspective-michael); Opus 5.5 high: fresh eyes, UI
20
13
4
3
2 (fixes)
0c4ce8e83bb
same 7 lenses on the fix diff
7
7
0
0
Final read
07d7e0ee544
GPT-6.1 Sol high
0
–
–
–
Fixed, among others: uploads checked workflow editor access only after the unchanged/superseded answers (a member without access could rewrite the source); a stale stored hash made the SDK loop on template_outdated; welcome\n passed key validation; two tickets each built a single-field PATCH matcher, a refusal exception and a usage-capture wrapper (now one each); editor saves sent key: null and failed after an adoption elsewhere; preview links replaced the preview; long keys clipped; Bitbucket and GitLab-subgroup source links were wrong. Risk lens graded the PR high (the unflagged save path plus public contracts), so a shadow diff was owed and ran.
Explicit danger checks
Email templates: an automatic upload never republishes a superseded hash #499 upload option:if_not_superseded reuses sent_hash_state; it now runs after the editor check. Live run on HEAD: the automatic upload of replaced content answers superseded and changes neither content nor ref; an explicit upload still rolls back.
Live run: Django from the branch on a scratch database, phs_ and personal keys, CDP stubbed at its HTTP boundary. Upload created/unchanged/updated, old hash 202 + template_superseded, made-up hash 409 with nothing queued, welcome\n 400, automatic upload superseded, a stale stored hash repaired by the next upload, edit 403 immutable, release, re-upload, adoption with a newline key 400. The earlier run on e52feb3ccd9 also covered a template written by master code (adopt, upload updated with the same id and workflow, send by key) and the flag-off 404s.
Shadow diff: base 3aa9e5a742e vs HEAD over 14 ordinary template edits, flag on and off: normalized responses identical, same workflow versions. Rerun on the final head: diff empty.
UI: Storybook renders on HEAD: a 90-character key truncates with an ellipsis and keeps its copy button at 560px; a target="_self" link in the preview opens a new tab and the preview stays; Duplicate shows a loading state while its request runs.
Prod shape: gap, accepted under the standing instruction (production read access from 2026-11-02).
Local gate on HEAD: messaging backend 385 passed, mypy clean, frontend tsgo clean, TemplateLibrary + git parser jest 83 passed, MCP typecheck and tool snapshots, tach, product:lint, pre-push ci:preflight --strict 0 failures.
Calls made (🤖, also in the PR's Decisions block)
Kept the spec as one PR (SHIP.md), although it is far over 400 production lines.
Repaired a stale hash at the upload boundary instead of hashing live on every send.
Declared the release (managed_by: gui) in the PATCH schema but kept it out of the MCP update tool, so agents can't unlock a template.
Moved the shared transactional email API pieces into their own module in this PR (the template view drops from 1,097 to under 900 lines).
Left the asset URL rewrite command outside the shared save path: recording only its hash would describe content the workflow does not send.
Rejected: why/fix/docs_url on existing send error codes (spec keeps their bodies); asserting the payload at the mocked CDP boundary is the observable output, not a change detector.
Graded the merge risk high; judged cross-version not owed; accepted the prod-shape gap under the standing instruction.
CodeRabbit is retired in this repo, so it was skipped.
rewrite_email_asset_url writes template content without the hash, the lock or the transactional workflow sync (the sync gap predates this work). Route it through save_template.
Release then re-upload race: a member who releases a template and edits it while an upload claims it back gets a 403 toast and an editable page until reload. Needs a choice about keeping their edits.
Activity
Silthus commentedon Oct 16, 2026
Integration branch
spec/code-managed-templatesis created on the fork froma5dbb10460b, the reviewed head of the #486 migration PR PostHog/posthog#118769. The ship PR stacks on that PR.Silthus commentedon Oct 17, 2026
Runtime: T3 child task
Task:
node:delegated-task:command%3Amcp%3A955e36b3-3986-469c-b816-09cf656780af%3Adelegate-task%3Awf-email-ship-498-1Cast: Opus 5.5, high. Cap 300 minutes. Ships
spec/code-managed-templatesat7845707ba33, stacked on PostHog#118769. CodeRabbit is retired in this repo, so no slot is held.Silthus commentedon Oct 17, 2026
Resolution
PR: PostHog/posthog#118924 (draft), branch
Silthus:spec/code-managed-templates, stacked on #118769 (review starts aftera5dbb10460b).Reviewed head:
07d7e0ee544c7ea081a14d5d7001611b31a68e66.fleet proof gateexits 0 on it.What shipped
upstream/master3aa9e5a742e. No conflicts;hogli build:openapiand the desktop client regenerated with no diff.e5f5ef26f53(template API),0c4ce8e83bb(template library UI),07d7e0ee544(round-2 findings).Review
qa-swarm, because the diff touches auth and API scopes, the save-and-sync transaction and public API contracts.e52feb3ccd9review-hog-perspective-michael); Opus 5.5 high: fresh eyes, UI0c4ce8e83bb07d7e0ee544Fixed, among others: uploads checked workflow editor access only after the
unchanged/supersededanswers (a member without access could rewrite the source); a stale stored hash made the SDK loop ontemplate_outdated;welcome\npassed key validation; two tickets each built a single-field PATCH matcher, a refusal exception and a usage-capture wrapper (now one each); editor saves sentkey: nulland failed after an adoption elsewhere; preview links replaced the preview; long keys clipped; Bitbucket and GitLab-subgroup source links were wrong. Risk lens graded the PR high (the unflagged save path plus public contracts), so a shadow diff was owed and ran.Explicit danger checks
if_not_supersededreusessent_hash_state; it now runs after the editor check. Live run on HEAD: the automatic upload of replaced content answerssupersededand changes neither content nor ref; an explicit upload still rolls back.partial_updateis refused incheck_object_permissions, so feat(workflows): draft and publish edits to transactional email templates PostHog/posthog#118664's draft-only PATCH is refused too). The client guard keepsdraftOutOfDatefalse, so autosave never fires. But feat(workflows): draft and publish edits to transactional email templates PostHog/posthog#118664 clears drafts in its update view, which the upload does not pass through, and itssplitDraftwould feed a leftover draft into the read-only preview. Nothing can change here beforedraft_contentexists, so the PR body lists the merge obligations for whichever lands second (clear drafts insave_locked_template, add "no draft" tounchanged, preview from published content, one mergedsetTemplateValuelistener). The spec's "holds whether feat(workflows): draft and publish edits to transactional email templates PostHog/posthog#118664 lands before or after" is not true as built.0007: feat(workflows): draft and publish edits to transactional email templates PostHog/posthog#118664 is still open and adds0007_messagetemplate_draft_content; feat(messaging): add key, owner, source and hash columns to templates PostHog/posthog#118769 adds0007_messagetemplate_code_managed_fields. Whichever lands second renumbers. Both PR bodies say so.Proof (HEAD
07d7e0ee544)phs_and personal keys, CDP stubbed at its HTTP boundary. Uploadcreated/unchanged/updated, old hash 202 +template_superseded, made-up hash 409 with nothing queued,welcome\n400, automatic uploadsuperseded, a stale stored hash repaired by the next upload, edit 403immutable, release, re-upload, adoption with a newline key 400. The earlier run one52feb3ccd9also covered a template written bymastercode (adopt, uploadupdatedwith the same id and workflow, send by key) and the flag-off 404s.3aa9e5a742evs HEAD over 14 ordinary template edits, flag on and off: normalized responses identical, same workflow versions. Rerun on the final head: diff empty.target="_self"link in the preview opens a new tab and the preview stays; Duplicate shows a loading state while its request runs.mypyclean, frontendtsgoclean, TemplateLibrary + git parser jest 83 passed, MCP typecheck and tool snapshots,tach,product:lint, pre-pushci:preflight --strict0 failures.Calls made (🤖, also in the PR's Decisions block)
managed_by: gui) in the PATCH schema but kept it out of the MCP update tool, so agents can't unlock a template.Follow-ups
rewrite_email_asset_urlwrites template content without the hash, the lock or the transactional workflow sync (the sync gap predates this work). Route it throughsave_template.