fix(daemon): preserve tenant scope for queued drains#1834
Merged
Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes a regression where queued prompts could stop draining after task completion by ensuring queue-drain work re-enters the correct tenant database scope even when invoked from completion/idle hooks, retries, or other minimal-params deferred paths.
Changes:
- Introduces a queue-specific tenant scoping helper (
runWithSessionQueueTenantScope/deferWithSessionQueueTenantScope) that can recover tenant context from params, ALS, trusted hidden tenant metadata, or static mode. - Preserves hidden (non-enumerable)
tenant_idmetadata on session DTOs acrossSessionRepositorymapping andupdate()return values so completion hooks can safely recover tenant context without exposing it via JSON. - Updates queue drain triggers (sessions after.patch and queue retry paths) to pass tenant-scoped params, and adds focused tests for tenant resolution behavior and hidden tenant metadata.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/core/src/db/repositories/sessions.ts | Attaches/preserves hidden tenant_id metadata on session DTOs (non-enumerable) for in-process tenant recovery. |
| packages/core/src/db/repositories/sessions.test.ts | Adds mapping test coverage ensuring tenant_id is preserved in-process but not exposed via JSON/enumeration. |
| apps/agor-daemon/src/utils/session-queue-tenant-scope.ts | Adds queue-specific tenant scope resolution + run/defer helpers for queue draining. |
| apps/agor-daemon/src/utils/session-queue-tenant-scope.test.ts | Adds tests for params/ALS/hint/static/fail-closed tenant resolution for queue draining. |
| apps/agor-daemon/src/register-routes.ts | Wraps queued-task processing + retry scheduling in session-queue tenant scoping. |
| apps/agor-daemon/src/register-hooks.ts | Uses trusted hidden tenant_id from patched session DTO to scope after.patch queue drain. |
| apps/agor-daemon/src/register-hooks.test.ts | Tests trusted hidden-tenant metadata reading without JSON exposure. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
mistercrunch
approved these changes
Jul 7, 2026
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.
Linked issue
Fixes #1831
Summary
Why / context
Queued prompts could stay stuck after a running task completed because completion-time queue drains depended on deferred tenant-scoped work. If the deferred path had minimal params, the drain could skip instead of starting the next queued task. Sending another prompt kicked the enqueue path, which processed the older queued task and left the newest one stuck.
Implementation notes
deferWithSessionQueueTenantScopeandrunWithSessionQueueTenantScoperesolve tenant context fromparams.tenant, current tenant ALS, trusted hidden tenant metadata from an already-returned session row, or configured static tenant mode.SessionRepositoryattaches non-enumerable hidden tenant metadata to session DTOs and preserves it throughupdate()return values; it is available in-process but not exposed through JSON serialization.triggerQueueProcessing/processNextQueuedTask.Validation / test plan
pnpm --filter @agor/daemon test -- src/utils/session-queue-tenant-scope.test.ts src/utils/tenant-db-scope.test.ts src/register-hooks.test.tspnpm --filter @agor/core test -- src/db/repositories/tasks.test.ts src/db/repositories/sessions.test.ts src/db/tenant-scope.test.tspnpm exec biome check apps/agor-daemon/src/utils/session-queue-tenant-scope.ts apps/agor-daemon/src/utils/session-queue-tenant-scope.test.ts apps/agor-daemon/src/register-hooks.ts apps/agor-daemon/src/register-hooks.test.ts apps/agor-daemon/src/register-routes.ts packages/core/src/db/repositories/sessions.ts packages/core/src/db/repositories/sessions.test.tsgit diff --checkpnpm check:multitenancy-boundariesremains red due existing baseline drift unrelated to this PR; touched-code classification is clean.Screenshots / demos
Not applicable; backend queue-processing fix.
Risks / rollout / rollback
Risk is concentrated in queue-drain tenant scoping and hidden tenant metadata propagation on session DTOs. The change is fail-closed for required multi-tenant paths without tenant context and preserves existing queue lock/order semantics. Rollback by reverting this PR.
Out of scope / follow-ups
A broader queue refactor/reconciler would make queue draining convergent and reduce reliance on fire-and-forget triggers; that is intentionally separate from this focused regression fix.