Skip to content

fix: stop recording ErrCompletedByStrategy as a span error (closes #4109) - #4597

Open
pacocartones wants to merge 2 commits into
ory:masterfrom
pacocartones:fix/4109-strategy-completion-not-span-error
Open

fix: stop recording ErrCompletedByStrategy as a span error (closes #4109)#4597
pacocartones wants to merge 2 commits into
ory:masterfrom
pacocartones:fix/4109-strategy-completion-not-span-error

Conversation

@pacocartones

@pacocartones pacocartones commented Aug 29, 2026

Copy link
Copy Markdown

Closes #4109.

The bug

ErrCompletedByStrategy is a control-flow signal (selfservice/flow/error.go), not a failure. The login handlers correctly suppress the client error for it (if !errors.Is(err, flow.ErrCompletedByStrategy) { WriteError }), but leave the tracked err set, so the deferred otelx.End(span, &err) records the signal as a span error/exception — a completed flow appears in telemetry as an error.

The fix

Clear the tracked err where it genuinely carries the signal, in the two handlers whose deferred otelx.End(span, &err) tracks it: createNativeLoginFlow (the reported one) and createBrowserLoginFlow. Client behavior and API are unchanged.

Scope note (checked, to avoid a no-op): the other update handlers listed around this issue are not affected — updateLoginFlow/updateSettingsFlow use a loop-shadowed err the deferred otelx.End doesn't track, and the registration/recovery/verification update handlers have no otelx.End(span, &err). So the fix is these two sites.

Test

New handler_completed_by_strategy_test.go: installs a tracetest span recorder as the registry tracer, forces a strategy to return ErrCompletedByStrategy, drives the real API endpoint, and asserts the createNativeLoginFlow span is not codes.Error and carries no exception event. Passes; reverting only the fix makes it fail with span status "flow response completed by strategy". (Focused test via in-memory mocks; the full login package's DB-backed suites need dockertest.)


Disclosure: this contribution was prepared with AI assistance (Claude Code); the fix, scope, and test were verified against HEAD.

Summary by CodeRabbit

  • Bug Fixes

    • Improved login, recovery, registration, verification, and settings flow handling when a strategy completes the response directly.
    • Prevented duplicate redirects or responses after a flow has already completed.
    • Ensured expected strategy-completed outcomes are not reported as errors in telemetry.
  • Tests

    • Added regression coverage confirming completed authentication flows return successfully and remain absent from error and exception telemetry.

The login handlers deliberately swallow flow.ErrCompletedByStrategy (a control
signal, not a failure) from the client response, but left the tracked err set, so
the deferred otelx.End(span, &err) recorded the control signal as a span
error/exception -- a completed flow shows up in telemetry as an error. Clear the
tracked err in the two handlers where it genuinely carries the signal
(createNativeLoginFlow, createBrowserLoginFlow). Client behavior and API are
unchanged. Adds a test asserting the span is not errored on strategy completion.

Closes ory#4109

Signed-off-by: pacocartones <pacocartones@users.noreply.github.com>
@pacocartones
pacocartones requested review from a team and aeneasr as code owners August 29, 2026 21:12
@CLAassistant

CLAassistant commented Aug 29, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@coderabbitai

coderabbitai Bot commented Aug 29, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: f3e10f23-2378-42dc-aa1e-53d23b50625f

📥 Commits

Reviewing files that changed from the base of the PR and between 9d863d2 and 92688d6.

📒 Files selected for processing (44)
  • selfservice/flow/error.go
  • selfservice/flow/login/handler.go
  • selfservice/flow/login/handler_completed_by_strategy_test.go
  • selfservice/flow/login/hook.go
  • selfservice/flow/login/strategy.go
  • selfservice/flow/recovery/handler.go
  • selfservice/flow/recovery/strategy.go
  • selfservice/flow/registration/handler.go
  • selfservice/flow/registration/strategy.go
  • selfservice/flow/settings/error_test.go
  • selfservice/flow/settings/handler.go
  • selfservice/flow/settings/strategy.go
  • selfservice/flow/verification/fake_strategy.go
  • selfservice/flow/verification/handler.go
  • selfservice/flow/verification/strategy.go
  • selfservice/strategy/code/strategy_login.go
  • selfservice/strategy/code/strategy_login_test.go
  • selfservice/strategy/code/strategy_recovery.go
  • selfservice/strategy/code/strategy_registration.go
  • selfservice/strategy/code/strategy_registration_test.go
  • selfservice/strategy/code/strategy_verification.go
  • selfservice/strategy/idfirst/strategy_login.go
  • selfservice/strategy/link/strategy_recovery.go
  • selfservice/strategy/link/strategy_verification.go
  • selfservice/strategy/lookup/login.go
  • selfservice/strategy/lookup/settings.go
  • selfservice/strategy/oidc/strategy.go
  • selfservice/strategy/oidc/strategy_login.go
  • selfservice/strategy/oidc/strategy_registration.go
  • selfservice/strategy/oidc/strategy_settings.go
  • selfservice/strategy/oidc/strategy_test.go
  • selfservice/strategy/passkey/passkey_login.go
  • selfservice/strategy/passkey/passkey_registration.go
  • selfservice/strategy/passkey/passkey_settings.go
  • selfservice/strategy/password/login.go
  • selfservice/strategy/password/registration.go
  • selfservice/strategy/password/settings.go
  • selfservice/strategy/profile/registration.go
  • selfservice/strategy/profile/strategy.go
  • selfservice/strategy/totp/login.go
  • selfservice/strategy/totp/settings.go
  • selfservice/strategy/webauthn/login.go
  • selfservice/strategy/webauthn/registration.go
  • selfservice/strategy/webauthn/settings.go
💤 Files with no reviewable changes (1)
  • selfservice/flow/error.go

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

Self-service flow strategies now return explicit completion signals instead of flow.ErrCompletedByStrategy. Flow handlers stop processing completed responses, and tracing no longer records completion control flow as errors.

Changes

Completion signal migration

Layer / File(s) Summary
Flow contracts and dispatch
selfservice/flow/*
Login, recovery, registration, settings, and verification interfaces and handlers now consume completed return values.
Strategy implementations
selfservice/strategy/*
Strategies return explicit completion values. OIDC and link strategies translate internal completion markers before telemetry finalization.
Regression coverage and call-site updates
selfservice/flow/*_test.go, selfservice/strategy/*_test.go
Tests and helper call sites use the updated return signatures and verify completed flows without errors.

Estimated code review effort: 4 (Complex) | ~45 minutes

Merge Risk: ⚪ Minimal · up to 92688

The completion-control refactor preserves completed-flow handling without treating it as an error, with no active merge-blocking risk identified.

Suggested reviewers: zepatrik

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.22% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 9 functions across 43 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes satisfy issue #4109 by replacing ErrCompletedByStrategy with completed boolean results across affected self-service flows and translating internal completion markers before tracing records…
Out of Scope Changes check ✅ Passed The changes across login, registration, recovery, verification, and settings are within scope because they remove the shared error-based completion signal and prevent false telemetry errors in all aff…
Title check ✅ Passed The title clearly identifies the primary fix: preventing ErrCompletedByStrategy from being recorded as a span error. The issue reference is relevant.
Description check ✅ Passed The description is detailed and on-topic. It explains the bug, the broader completion-signal refactor, the affected scope, the linked issue, and the regression test. It does not reproduce the template…
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gaultier

gaultier commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Thanks for the contribution. The intent is good, but there are two issues:

  • Not all sites are fixed
  • This error as you pointed is not a real error.

Thus a better fix is to make this control value not an error, but instead an additional return value.

…, not an error

flow.ErrCompletedByStrategy was a control signal ("the strategy already
wrote the response, stop processing"), not a failure. Because it was
modelled as an error and returned through functions whose deferred
otelx.End(span, &err) tracked that err, a completed flow was recorded as
a span error/exception -- most visibly on a normal OIDC login, which
produced several false-positive errored spans (ory#4109). Clearing the err
at a single handler only patched one of many sites.

Remove the sentinel entirely and thread completion as an additional
return value across the strategy interfaces and their callers:

- login.Strategy.Login and FastLoginStrategy, registration.Strategy.Register,
  recovery.Strategy.Recover, verification.Strategy.Verify and
  settings.Strategy.Settings now return a `completed bool`.
- Handler.NewLoginFlow returns `completed bool`; the login/registration/
  recovery/verification/settings handlers and the login hook consume it.
- Strategy implementations (code, oidc, webauthn, passkey, idfirst, profile,
  password, totp, lookup, link) return the bool from their completion paths.
- Where a strategy's internal call graph is deep (oidc HandleError account
  linking; the code and link recovery/verification chains), an unexported
  package-local sentinel carries completion internally and is translated to
  the bool at the package's exported entry point, so completion never crosses
  the interface as an error.

Deleting flow.ErrCompletedByStrategy makes the compiler enforce that every
producer, consumer and comparison site is converted. A completed flow can no
longer be recorded as a span error at any site.

Closes ory#4109

Signed-off-by: pacocartones <pacocartones@users.noreply.github.com>
@pacocartones

Copy link
Copy Markdown
Author

Thanks @gaultier — both points addressed.

I removed ErrCompletedByStrategy entirely and modelled completion as an additional return value (completed bool) across the strategy interfaces (Login / FastLogin1FA / FastLogin2FA, Register, Recover, Verify, Settings) and Handler.NewLoginFlow, threading it through every implementation and caller. Deleting the sentinel let the compiler confirm no producer, consumer or comparison was missed.

On "not all sites": the original change only cleared the two login create-handler spans. The errored spans in your screenshot actually came from the internal OIDC strategy spans (oidc.Login / processLogin), which are now clean because completion is never an error at any layer. Where a strategy's internal call graph is deep — OIDC account-linking in HandleError, and the code/link recovery+verification chains — an unexported package-local marker carries completion internally and is translated to the completed value (and neutralised before the span's otelx.End) so it never surfaces as an error or an errored span.

go build ./... and go vet ./... pass, gofmt is clean, and the focused telemetry regression test passes; the DB-backed self-service suites will run in CI.

It's a broad change — it touches every self-service flow (44 files) — because the sentinel deletion is the only thing that unambiguously closes "not all sites". If you'd rather review it incrementally, it splits cleanly per flow (login first, then registration / recovery / verification / settings) — just say the word and I'll break it up.

@pacocartones pacocartones changed the title fix(selfservice): do not record ErrCompletedByStrategy as a span error (closes #4109) fix: stop recording ErrCompletedByStrategy as a span error (closes #4109) Sep 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ErrCompletedByStrategy is not an error, but shows up in telemetry as such.

3 participants