Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix: remove excess calls to prepareSQLFilter for workspace and template endpoints - #27248

Merged
cstyan merged 7 commits into
mainfrom
callum/workspaces-double-prepare
Jul 15, 2026
Merged

fix: remove excess calls to prepareSQLFilter for workspace and template endpoints#27248
cstyan merged 7 commits into
mainfrom
callum/workspaces-double-prepare

Conversation

@cstyan

@cstyan cstyan commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

This PR removes redundant (could be considered duplicate) prepareSQLFilter calls on the workspaces and templates paths. This is meant to partially address #21890.

The prepareSQLFilter is particularly expensive for users who are a member of many (in the order of 100) organizations. In those cases, the relevant paths (OPA and others) account for more than 50% of the total CPU time. A large part of this appears to be second hand as a result of allocations on those paths.

Note: the benchmark is AI written, and as usual it hallucinated some things in the first few rounds of implementing it.

Benchmark results:

pkg: github.com/coder/coder/v2/coderd
cpu: AMD EPYC 9575F 64-Core Processor
                               │ /tmp/before.txt │           /tmp/after.txt           │
                               │     sec/op      │   sec/op     vs base               │
WorkspacesHandler/orgs=1-128         5.705m ± 3%   2.889m ± 4%  -49.36% (p=0.002 n=6)
WorkspacesHandler/orgs=10-128        26.60m ± 4%   13.32m ± 2%  -49.94% (p=0.002 n=6)
WorkspacesHandler/orgs=50-128        287.6m ± 1%   145.1m ± 1%  -49.56% (p=0.002 n=6)
WorkspacesHandler/orgs=100-128       989.5m ± 0%   504.0m ± 2%  -49.07% (p=0.002 n=6)
geomean                              81.07m        40.95m       -49.48%

                               │      B/op       │     B/op      vs base               │
WorkspacesHandler/orgs=1-128        2.171Mi ± 0%   1.101Mi ± 0%  -49.28% (p=0.002 n=6)
WorkspacesHandler/orgs=10-128       9.888Mi ± 0%   4.998Mi ± 0%  -49.45% (p=0.002 n=6)
WorkspacesHandler/orgs=50-128      102.87Mi ± 0%   51.68Mi ± 0%  -49.77% (p=0.002 n=6)
WorkspacesHandler/orgs=100-128      354.2Mi ± 0%   177.6Mi ± 0%  -49.87% (p=0.002 n=6)
geomean                             29.74Mi        14.99Mi       -49.59%

                               │    allocs/op    │  allocs/op   vs base               │
WorkspacesHandler/orgs=1-128         66.12k ± 0%   33.34k ± 0%  -49.57% (p=0.002 n=6)
WorkspacesHandler/orgs=10-128        349.4k ± 0%   175.4k ± 0%  -49.79% (p=0.002 n=6)
WorkspacesHandler/orgs=50-128        4.027M ± 0%   2.016M ± 0%  -49.93% (p=0.002 n=6)
WorkspacesHandler/orgs=100-128      14.178M ± 0%   7.094M ± 0%  -49.96% (p=0.002 n=6)
geomean                              1.072M        537.8k       -49.81%

@cstyan

cstyan commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@coder-agents-review

coder-agents-review Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Chat: Review posted | View chat
Requested: 2026-07-15 00:45 UTC by @cstyan
Spend: $45.29 / $100.00

Review history
  • R1 (2026-07-14): 18 reviewers, 4 Nit, 2 Note, 1 P2, COMMENT. Review
  • R2 (2026-07-15): 16 reviewers, 5 Nit, 4 Note, 1 P2, 1 P3, COMMENT. Review

deep-review v0.9.0 | Round 2 | f84801e..f399563

Last posted: Round 2, 11 findings (1 P2, 1 P3, 5 Nit, 4 Note), COMMENT. Review

Finding inventory

Finding inventory, PR #27248

Findings

# Sev Status Location Summary Round Reviewer Posted
CRF-1 P2 Author fixed R2 (f399563); verified by revert (Netero/Bisky) orgscale_bench_test.go:131 (deleted) Benchmark never exercises the changed handler path; cannot reproduce before/after and guards no regression R1 Bisky P2, Razor P2, Zoro P2, Mafu-san P2, Mafuuu P3, Pariston P3, Killua P3, Ryosuke P3, Meruem P3, Netero Note Yes
CRF-2 Nit Open; re-raised R2 at workspaces_bench_internal_test.go:49 orgscale_bench_test.go:101 (deleted) Redundant n := n loop-variable copy (dead since Go 1.22) R1 Netero, ging-go, Hisoka, Kite, Chopper, Meruem, Zoro, Gon Yes
CRF-3 Nit Dropped by orchestrator R2 (matches codebase convention: 17 files use ResetTimer+b.N vs 1 b.Loop; not lint-enforced) orgscale_bench_test.go:130 (deleted) Use for b.Loop() (Go 1.24+); subsumes b.ResetTimer() R1 ging-go Yes
CRF-4 Nit Open; re-raised R2 at workspaces_bench_internal_test.go:64 orgscale_bench_test.go:39 (deleted) Use for range n where index is unused R1 ging-go Yes
CRF-5 Nit Author fixed R2 (old file deleted; comments gone) orgscale_bench_test.go:96 (deleted) Benchmark comments restate adjacent code (lines 55, 96, 107) R1 Gon Yes
CRF-6 Note Panel closed R2 (Leorio, Chopper, Mafuuu, Pariston: Detail preserves cause, layering acceptable) workspaces.go:182 Prepare-failure 500 text collapses into the generic fetch error; detail preserved R1 Chopper, Mafuuu Yes
CRF-7 Note Open R2; needs human decision (wrappers now dead outside tests/mocks; file ticket or accept) dbauthz.go:9188 GetAuthorized* wrappers accept a PreparedAuthorized they discard; class persists until wrappers are deleted (TODO exists) R1 Ryosuke, Meruem Yes
CRF-8 P3 Open workspaces_bench_internal_test.go:31 Benchmark/setup comments narrate the fix/commit workflow ("the fix", "fix commit", "pre-fix handler", "both revisions") instead of code behavior; dangles after merge R2 Gon P2 Yes
CRF-9 Nit Open (in body) commit f399563 subject Test commit subject 78 chars, past the 72-char limit R2 Leorio No
CRF-10 Note Open workspaces_test.go:88 Exact PrepareCount==1 depends on no owner-subject concurrent work; correct today but not enforced R2 Komugi Yes
CRF-11 Nit Open coderdtest/authorize.go:517 PrepareCountingAuthorizer lacks the var _ rbac.Authorizer assertion its siblings carry R2 Robin Yes
CRF-12 Note Open workspaces_test.go:65 Regression test spins a provisioner + full build to seed one workspace where dbfake would suffice R2 Meruem Yes

Round log

Round 1

Netero first pass (Nit + Note, P3-and-below) then 18-reviewer panel. Reviewed against f84801e..e010e3d.
Production change (remove duplicate handler-level AuthorizeSQLFilter on workspaces/templates list paths) verified correct and behavior-preserving by 10+ reviewers: dbauthz.GetAuthorizedWorkspaces/GetAuthorizedTemplates (dbauthz.go:9188, 9159) discard their PreparedAuthorized arg and re-derive the filter via prepareSQLFilter from the context actor. api.Database is always the dbauthz-wrapped store (coderd.go:403). Class fully addressed for list paths; only other AuthorizeSQLFilter caller (workspaceagents.go:2273, ActionSSH) consumes its result and was correctly left alone.
Perf win is real: authCache.Prepare (authz.go:805) is a documented pass-through with no caching, so removing one of two full OPA prepares halves per-request prepare cost. Resolved Killua/Razor disagreement in favor of Razor via orchestrator verification.
1 P2, 4 Nit, 2 Note. Event COMMENT (no P0/P1).

Round 2 update

Churn guard: PROCEED (5 addressed, 2 silent Notes, 0 contested). Author reworked against f84801e..f399563.
CRF-1 addressed by rewrite: added coderdtest.PrepareCountingAuthorizer helper, two regression tests (TestWorkspacesListSingleAuthorizePrepare, TestTemplatesListSingleAuthorizePrepare) asserting PrepareCount==1, and a handler-level BenchmarkWorkspacesHandler (package coderd) that runs the real api.workspaces. Old misaimed benchmark deleted. Panel to verify the tests actually guard the dedup and the benchmark faithfully represents the handler.
CRF-2/3/4 (loop-var and benchmark-idiom nits) recur verbatim in the new benchmark file; panel (ging-go) to re-raise on the new locations. CRF-5 fixed (old comments gone). CRF-6/7 silent Notes, non-gating.
Production authz change (workspaces.go/templates.go) unchanged from R1.

Round 2 (panel)

16-reviewer panel + Netero. Reviewed against f84801e..f399563. CRF-1 verified fixed (revert checks). CRF-5 fixed. CRF-6 closed (layering acceptable). CRF-3 dropped (codebase convention). CRF-2, CRF-4 re-raised on new file. CRF-7 strengthened, needs human decision. New: CRF-8 (P3 comment narration), CRF-9 (Nit commit subject), CRF-10 (Note latent flake), CRF-11 (Nit missing interface assertion), CRF-12 (Note provisioner vs dbfake). No P0/P1. Event COMMENT.

About deep-review

CRF = Coder Review Finding (P0-P4, Nit, Note)

Reviewer Focus
Bisky tests
Chopper ops/errors
Churn-guard change verification
Ging language modernization
Gon naming
Hisoka edge cases
Killua perf
Kite change integrity
Knov contracts
Knuckle SQL
Komugi flake/determinism
Kurapika security
Law decomposition
Leorio docs
Luffy product
Mafu-san process
Mafuuu contracts
Melody dispatch/pairing
Meruem structural
Nami frontend
Netero mechanical checks
Pariston premise testing
Pen-botter product gaps
Razor verification
Robin duplication
Ryosuke Go arch
Takumi concurrency
Zoro shape

🤖 Managed by Coder Agents.

@coder-agents-review coder-agents-review Bot left a comment

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.

Round 1 review. The production change is small, correct, and well-scoped: it removes a genuinely redundant handler-level AuthorizeSQLFilter prepare on the workspaces and templates list paths. The panel verified the safety independently across many reviewers: api.Database is always the dbauthz-wrapped store (coderd.go:403), and GetAuthorizedWorkspaces/GetAuthorizedTemplates (dbauthz.go:9188, 9159) discard their PreparedAuthorized argument and re-derive the SQL filter from the context actor via prepareSQLFilter. So the removed prepare was dead work built and thrown away every request; authorization scoping is unchanged and there is no security regression. The bug class is fully addressed for the list paths, and the one remaining AuthorizeSQLFilter caller (workspaceagents.go:2273, ActionSSH) actually consumes its result and was correctly left alone. The comments added at the call sites are model why-not-what notes. The perf win is real: authCache.Prepare (authz.go:805) is a documented pass-through with no caching, so dropping one of two full OPA prepares halves per-request prepare cost.

One P2, four Nits, two Notes. No P0/P1, so this is a COMMENT, not a change request.

The P2 is the benchmark, and it is worth taking seriously despite being test-only. The committed BenchmarkDBAuthzGetWorkspaces calls dbauthz.GetWorkspaces directly, a method this PR does not touch, so it performs exactly one prepare on both base and HEAD. Three reviewers ran it on both commits and got identical numbers matching the PR body's "after" column, so the committed artifact cannot reproduce the before/after table it is presented as proving, and it will stay green if the exact regression this PR fixes is reintroduced. The benchmark is a legitimate characterization of single-prepare OPA cost scaling with org count (useful for #21890); the ask is to stop presenting it as before/after proof, or to make it guard the handler path.

As Hisoka put it: "I came to fight this diff. It didn't fight back." The change is sound; the evidence artifact just measures a layer below the change.

Process note: thank you for disclosing that the benchmark is AI-written and for scoping the PR as a partial fix for #21890. That honesty is the right call and is exactly why the benchmark's aim is worth correcting rather than trusting.


coderd/database/dbauthz/dbauthz.go:9188

Note [CRF-7] The GetAuthorized* wrappers accept a PreparedAuthorized they silently discard, which is the trap that produced this redundancy (Ryosuke, Meruem).

GetAuthorizedWorkspaces(ctx, arg, _ rbac.PreparedAuthorized) (and GetAuthorizedTemplates at :9159) invite callers to compute and pass a prepared filter that is thrown away and re-derived internally. Both already carry // TODO Delete this function. This PR removes the two live misuses, but until those wrappers are deleted and the interface entries removed, a future caller can reintroduce the exact double-prepare and the type system will not object. Out of this PR's scope, but that is where the class actually lives.

🤖

🤖 This review was automatically generated with Coder Agents.

Comment thread coderd/database/dbauthz/orgscale_bench_test.go Outdated
Comment thread coderd/database/dbauthz/orgscale_bench_test.go Outdated
Comment thread coderd/database/dbauthz/orgscale_bench_test.go Outdated
Comment thread coderd/database/dbauthz/orgscale_bench_test.go Outdated
Comment thread coderd/database/dbauthz/orgscale_bench_test.go Outdated
Comment thread coderd/workspaces.go
cstyan added 2 commits July 15, 2026 00:39
The GET /api/v2/workspaces handler called AuthorizeSQLFilter to build a
prepared ResourceWorkspace authorizer and passed it to
Database.GetAuthorizedWorkspaces. The dbauthz wrapper ignores that
argument and re-prepares its own SQL filter inside GetWorkspaces, so the
handler-level prepare was dead work: every request ran OPA partial
evaluation twice.

Partial evaluation cost scales with the number of organization-scoped
roles the subject carries, so for users in many organizations this
doubled an already expensive operation (see #21890). Call GetWorkspaces
directly, which authorizes the query itself with a single prepare.
Same redundant double OPA partial evaluation as the workspaces list: the
GET /api/v2/templates handler called AuthorizeSQLFilter to build a
prepared ResourceTemplate authorizer, but the dbauthz
GetAuthorizedTemplates wrapper ignores it and re-prepares its own SQL
filter inside GetTemplatesWithFilter. Call GetTemplatesWithFilter
directly, which authorizes the query itself with a single prepare.

Like the workspaces change, this halves the OPA partial-evaluation cost
of the templates list for users in many organizations (see #21890).
@cstyan
cstyan force-pushed the callum/workspaces-double-prepare branch from e010e3d to f399563 Compare July 15, 2026 00:41
@cstyan

cstyan commented Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@coder-agents-review coder-agents-review Bot left a comment

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.

Round 2. The R1 P2 is fixed, and fixed well. The author replaced the misaimed benchmark with a handler-level BenchmarkWorkspacesHandler that runs the real api.workspaces, and added two regression tests (TestWorkspacesListSingleAuthorizePrepare, TestTemplatesListSingleAuthorizePrepare) plus a PrepareCountingAuthorizer helper that asserts exactly one OPA prepare per list request. The panel verified these are genuine guards, not assumptions: reverting the handler to base makes the tests fail with PrepareCount==2 (Netero on workspaces, Bisky on templates), and the assertion also fails at 0, so it pins exactly-once in both directions. Determinism was checked under -count=20 -race (Komugi) and -count=5 (Hisoka, Mafuuu), all green. The PrepareCountingAuthorizer does not duplicate RecordingAuthorizer (that records Authorize; this records Prepare, which the caching authorizer does not dedupe). Strong response to the round 1 feedback.

As Hisoka put it: "I came to fight this change and it fought back. Round 2, and the opponent got stronger."

No P0/P1 this round, so this stays a COMMENT. New findings are one P3 and a few Nits/Notes, all minor.

Dispositions on prior findings: CRF-1 verified fixed (resolved). CRF-5 fixed (old comments deleted with the file). CRF-6 (prepare-failure error text) is closed: Leorio, Chopper, Mafuuu, and Pariston all confirmed the Detail field preserves the underlying OPA cause, so the coarser top-line Message is acceptable, not a lost diagnosis. CRF-3 (for b.Loop()) is dropped: both Netero and ging-go note the codebase overwhelmingly uses the b.ResetTimer()+b.N form (17 files vs 1) and it is not lint-enforced, so the current code matches the prevailing convention.

CRF-7 needs a human decision, not another review round. This PR removed the last production callers of the dbauthz GetAuthorizedWorkspaces/GetAuthorizedTemplates wrappers, so those methods (which accept a PreparedAuthorized they discard) are now dead outside tests and mocks, and their // TODO Delete this function is unblocked. dbauthz.go is out of this PR's scope, so either file a follow-up ticket to delete the wrappers and their interface entries, or explicitly accept keeping the misleading signature. A silent deferral is a drop.

Process notes: the ground-up benchmark rewrite reintroduced the three R1 loop/idiom nits verbatim (CRF-2, CRF-4; CRF-3 dropped per above), so prior-round feedback did not fold into the rewrite. Separately, the test commit subject test(coderd): guard workspaces/templates list against double authorize prepare is 78 characters, past the 72-char limit, so git log --oneline clips "prepare"; something like test(coderd): guard list handlers against double authorize prepare (66) keeps it intact.

🤖 This review was automatically generated with Coder Agents.

// to a growing number of organizations.
//
// This benchmark exercises the handler body itself, not dbauthz.GetWorkspaces
// directly, because the double-prepare fix changed only the handler: the

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.

P3 [CRF-8] Benchmark and setup comments describe the PR's fix/commit workflow instead of the code's behavior (Gon P2).

The doc paragraph explains the benchmark via "the double-prepare fix changed only the handler", "The fix calls Database.GetWorkspaces directly", and "a benchstat comparison across the fix commit" / "on both revisions"; the HTTPAuth comment (:159) reads "used by the pre-fix handler path ... on both the pre-fix and post-fix revisions"; and the captureAuthedContext comment (:168) references "AuthorizeSQLFilter in the pre-fix handler". After merge there is no "fix commit" or "pre-fix handler" in the tree, so these dangle. AGENTS.md asks comments to "describe the behaviour of the code, not the reasoning the agent used to produce the change."

Orchestrator judgment: P3, not P2. This is comment quality in a test file with no behavioral impact, and Meruem confirmed the HTTPAuth field itself is legitimately kept so the benchmark can be checked out and run against the base revision for benchstat. The ask is narrow: keep the field, but phrase the rationale in durable present tense (the per-request Prepare cost lives in the handler, so the benchmark drives the handler) rather than in terms of a fix/commit comparison that vanishes on merge.

🤖

orgCounts := []int{1, 10, 50, 100, 200}

for _, n := range orgCounts {
n := n

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.

Nit [CRF-2] Redundant n := n loop-variable copy, dead since Go 1.22 (ging-go, Netero, and re-verified by Bisky, Hisoka, Mafu-san, Razor, Meruem).

This is CRF-2 from round 1 recurring verbatim at a new location after the rewrite. go.mod declares go 1.26.4, so each for _, n := range orgCounts iteration already binds a fresh n and the b.Run closure captures it correctly. Delete the line.

🤖

// real request produces (fully expanded roles, cached AST value).
roleNames := make([]string, 0, n+1)
roleNames = append(roleNames, rbac.RoleMember().String())
for i := 0; i < n; i++ {

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.

Nit [CRF-4] for i := 0; i < n; i++ with an unused index; use for range n (ging-go, Netero).

CRF-4 from round 1, recurring at a new location. The body uses uuid.New() and never reads i, so for range n (the range-over-int form the rest of the codebase uses) says exactly what the loop does.

🤖

Comment thread coderd/coderdtest/authorize.go Outdated
// the counts reflect every partial evaluation performed. Counts are keyed by
// subject ID so a test can isolate the prepares made on behalf of a specific
// user and ignore background work performed under system subjects.
type PrepareCountingAuthorizer struct {

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.

Nit [CRF-11] PrepareCountingAuthorizer is the only authorizer double in this file without a compile-time interface assertion (Robin).

Its siblings carry one: var _ rbac.Authorizer = (*RecordingAuthorizer)(nil) (:154) and var _ rbac.Authorizer = (*FakeAuthorizer)(nil) (:374). The new type satisfies the interface only through the embedded rbac.Authorizer, so a method-set mistake would compile silently; the assertion documents intent and matches the local convention. Add var _ rbac.Authorizer = (*PrepareCountingAuthorizer)(nil).

🤖

Comment thread coderd/workspaces_test.go
authz.Reset()
res, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{})
require.NoError(t, err)
require.Len(t, res.Workspaces, 1)

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.

Note [CRF-10] The exact PrepareCount == 1 assertion holds only while no owner-subject work runs concurrently with the measured request (Komugi).

This is guaranteed today by three facts outside the test: background reconcilers use system subjects, the default coderdtest autobuild ticker never fires, and setup owner requests are drained before Reset(). Verified stable under -count=20 -race. Worth knowing (not changing now): if a future change adds periodic owner-scoped work, or a test in this package enables a firing autobuild ticker that triggers an owner-context prepare, this exact-equality assertion and its twin at templates_test.go:71 will flake at count 2. The tight assertion is the right guard; this is a latent constraint to keep in mind.

🤖

Comment thread coderd/workspaces_test.go
Add a PrepareCountingAuthorizer to coderdtest that counts
Authorizer.Prepare calls per (subject, action, objectType), and use it in
TestWorkspacesListSingleAuthorizePrepare and
TestTemplatesListSingleAuthorizePrepare to assert each list request runs
OPA partial evaluation for its resource exactly once. These fail if the
redundant handler-level prepare is reintroduced.

Add BenchmarkWorkspacesHandler, which drives the real GET /workspaces
handler over a mocked database for a user in a growing number of
organizations, so the whole authorization path a request takes is
measured and its cost scaling with org count is visible (see #21890).
@cstyan
cstyan force-pushed the callum/workspaces-double-prepare branch from f399563 to d7e9248 Compare July 15, 2026 02:06

@Emyrk Emyrk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

overall LG.
Can we reuse the RecordingAuthorizer and extend it rather than make a new testing tool though?

Comment thread coderd/coderdtest/authorize.go Outdated
Comment thread coderd/templates.go
templates, err := api.Database.GetAuthorizedTemplates(ctx, args, prepared)
// GetTemplatesWithFilter authorizes the query itself, so we don't
// prepare a SQL filter here.
templates, err := api.Database.GetTemplatesWithFilter(ctx, args)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

// Partial evaluation cost is driven by the subject, not the object type: OPA
// expands the policy against the subject's N org-scoped roles, so it scales
// with org count (see #21890).
func BenchmarkWorkspacesHandler(b *testing.B) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your call if we need to keep this benchmark

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed, it's easy enough to come back and use this PR as a reference if we end up needing a benchmark here again in the future 👍

cstyan added 2 commits July 15, 2026 18:17
Replace the standalone PrepareCountingAuthorizer with prepare counting on
the existing RecordingAuthorizer. Prepare calls are recorded into a
separate Prepared slice (kept out of Called so existing Authorize-call
assertions are unaffected), and PrepareCount/Reset expose and clear them.
The single-prepare guard tests inject a plain RecordingAuthorizer instead
of a bespoke type.
BenchmarkWorkspacesHandler served its purpose: it quantified the double
to single OPA prepare reduction on the list handler while that change was
under review. The prepare-count guard tests
(TestWorkspacesListSingleAuthorizePrepare,
TestTemplatesListSingleAuthorizePrepare) provide the lasting regression
protection, so the benchmark and its capture-once helper are no longer
needed. This commit can be referenced if the handler-over-mock benchmark
is ever needed again.
@cstyan
cstyan merged commit 15da504 into main Jul 15, 2026
50 of 51 checks passed
@cstyan
cstyan deleted the callum/workspaces-double-prepare branch July 15, 2026 21:23
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 15, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants