Repository navigation
Conversation
Brings back the one-minute member snapshot that sends share, with a fresh lookup right before delivery, and caps open lookups at five with deadline-slots. A lookup that gets its connection after its deadline still runs no query. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
🤖 CI report✅ Trunk lane — non-backend laneThis PR is assigned to the non-backend lane. It does not run backend Python tests and may merge in parallel with PRs in other lanes. ✅ Duplication (Python) — cleanNew Python code duplication introduced by this branch. Fails at 70+ tokens in app code, or 150+ tokens when both copies live in test files. Advisory while the gate proves itself: extract a shared helper instead of copying. ✅ Duplication (TypeScript) — cleanNew TypeScript code duplication introduced by this branch. Fails at 70+ tokens in app code, or 150+ tokens when both copies live in test files. Advisory while the gate proves itself: extract a shared helper instead of copying. ✅ Comment density — 2% of added code lines are comments (11 of 636)This section warns when comments are more than 3% of the code lines a PR adds, and alerts above 6%. Before agent-assisted PRs, the typical share was about 2%. Only full-line comments count. Docstrings, generated files, snapshots, migrations, and workflow files are left out. Comments that restate the code, record how the change came about, or narrate the next line add noise for the next reader. Keep the comments that explain a reason the code cannot show, and remove the rest. See Files with the most added comment lines:
This check does not block merging. It updates on every push and clears when the share drops. |
Move the member lookup's deadline, statement timeout, slot bound and late-connection guard into BoundedPrimaryLookup, so the sandbox pause read can use the same protection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lows-sandbox-split/member-cache # Conflicts: # nodejs/src/cdp/services/managers/organization-members.service.ts
…eased Bounded primary lookups now run through the deadline transaction. A connection that arrives after the deadline sends no SQL, and one still busy at the deadline, BEGIN included, is destroyed, so its slot frees for the next lookup. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lows-sandbox-split/member-cache
…box-split/member-cache
…box-split/member-cache Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…box-split/member-cache Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mber checks Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…box-split/member-cache
…box-split/member-cache Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…box-split/member-cache Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…box-split/member-cache Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…box-split/member-cache Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…box-split/member-cache Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…box-split/member-cache Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…box-split/member-cache Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…box-split/member-cache Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…box-split/member-cache Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…box-split/member-cache Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…box-split/member-cache
…box-split/member-cache Carries the null-verification rule into the cached snapshot and fixes the member cache in the same resolution: - the LazyLoader refresh age alone bounds snapshot age; the second expiry clock, its injected now and both expiry branches are gone - a cached block is checked again fresh, and a fresh block marks the cache stale, so neither a new member nor a removed one waits a minute - getBlockedRecipients and checkSandboxRecipients take fresh as required - Semaphore gains a give-up signal and replaces DeadlineSlots - the cache holds at most 10,000 teams Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New commits were pushed — dismissing the stamphog approval from an earlier head. Re-add the trigger label to request a fresh review.
…box-split/member-cache # Conflicts: # nodejs/src/cdp/services/managers/organization-members.service.ts
A cached block ran its own uncached lookup and then marked the team stale, so a workflow mailing non-members cost two primary lookups per send that no concurrent send shared. A block from a snapshot older than 5 seconds now reloads it through the cache instead, so concurrent and later sends reuse that one reload. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…box-split/member-cache
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…box-split/member-cache
A cached block older than 5 seconds marked its team stale before it reloaded. When that reload failed, the entry stayed stale, so every later send for the team waited on the primary and failed while it was down. The block now reloads without marking the entry first, so a failed reload fails only that send and the old snapshot keeps serving the rest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A reload joined a load already running for the key, so it could return a value read before the caller's write. It now joins only a buffered load that has not started, and an earlier load that finishes later no longer overwrites the newer value. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…box-split/member-cache
…box-split/member-cache
A reload skipped a load already running, so every blocked send that came in while a reload query ran started another one. A reload now joins a reload of the same key that is already in flight, and otherwise starts a load after the call, as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…box-split/member-cache
A reload that joined another reload's running query returned the value from before its own call, so a member who verified or joined during that query was blocked for the next snapshot window. Join a reload only while its query has not started. Callers that arrive later share one queued reload that starts when the running one settles, so a key still has at most two member queries in flight. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A reload queued behind one that failed started another lookup, so under a stalled primary each queued send waited for two lookup deadlines and added a query to the struggling pool. Queued reloads now fail with the running one, and the next reload after that loads again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…box-split/member-cache Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…box-split/member-cache Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New commits were pushed — dismissing the stamphog approval from an earlier head. Re-add the trigger label to request a fresh review.
|
Note 🤖 Claude Opus 5.5 responding on behalf of Michael Replaced by #131731 in the isolated sandbox rebuild. |
Problem
Merge danger
Caution
🔴 Merge risk: high · two-way door (merge-risk rubric)
LazyLoaderis shared by most Node caches, andSemaphoreby hog function execution. A bug in either can serve stale values or stall work for most teams.getandrunpaths keep their behavior, and new tests pin the reload and give-up paths. The member check runs only for the sandbox sender.git revertthe merge commit. Nothing to clean up.Changes
Semaphore.runtakes a give-up signal for this, and others fail closed at the 3 second deadline.LazyLoader.reloadjoins another reload only before its query starts. Later callers share one queued follow-up, so a key gets at most two member queries per burst.BoundedPrimaryLookupholds the deadline, statement timeout and slot cap. The pause check in #129054 reuses it.How did you test this code?
lazy-loader.test.tsshows a reload that sees a write made during another reload, one shared follow-up load, and queued reloads failing with the running one.organization-members.service.test.tscounts 3 queries for six blocked sends during a held reload, and covers sharing, expiry, the 5 second recheck and the five-lookup cap.sempahore.test.tsshows that a caller that gives up leaves the queue without running.Earlier runs at older heads, not this head
Live run at the #129052 head bb10d28, which contains this PR. Same devbox kit as #128946: private Django and Node
cdp-api, a seeded org, a local server that records each SES request, and the dev Valkey for the allowances. Sends used the editor's "Run test" path.Member cache live at c3050aa, an earlier head of this PR. The real
OrganizationMembersServiceran against the kit database, with a second session locking the membership table to stall lookups.Flag-off parity at older head (bea3fbe). Own-domain sends must stay unchanged while the sandbox sender is off. A scratch Jest harness sent four own-domain sends through the real
EmailService, on masterfb75476b8a9and on that head. A disabled sandbox sender and the member lookup were wired in as spies.team-<id>and configuration setposthog-messagingmatch.031a2c3d448.Test rationale: each case catches a member check that serves a stale allow, storms the primary, or holds a slot for good.
Release status
Docs update
docs/internal/workflows-email-sending-tiers.mdsays the membership snapshot expires within one minute.🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Agent: Claude Code, Claude Opus 5.5
Skills:
/writing-tests,/writing-code-comments,/stacking-prs,/writing-pr-descriptionsMore context for AI agents
deadline-slotswith a give-up signal onSemaphore, and dropped the second expiry clock forLazyLoader's own.LazyLoaderandSemaphore, which most Node paths share.is_email_verifiedmembers are blocked; that decision stays open.reloadinnodejs/src/common/utils/lazy-loader.ts,BoundedPrimaryLookup.run, thengetBlockedRecipientsinorganization-members.service.ts.Stack decisions, moved here from the visible body:
sandbox, created only by PostHog codeWORKFLOWS_SANDBOX_SENDER_ENABLED, no feature flag in CDP Node codeBoundedPrimaryLookup, shared by the member and pause checksBEGIN🤖 Generated with Claude Code