Skip to content

fix: use detached context for CreateMember rollback - #169

Merged
bramwelt merged 2 commits into
mainfrom
fix/LFXV2-2984-rollback-detached-context
Aug 5, 2026
Merged

fix: use detached context for CreateMember rollback#169
bramwelt merged 2 commits into
mainfrom
fix/LFXV2-2984-rollback-detached-context

Conversation

@bramwelt

@bramwelt bramwelt commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Summary

  • CreateMember rollback deferred func was calling deleteMemberKeys with the HTTP request context, which is cancelled when the client times out (e.g. v1-sync-helper's 30-second http.Client.Timeout under onboarding load)
  • NATS KV cleanup operations all fail immediately on a cancelled context, leaving the uniqueness key (lookup/member/<sha256-hash>) orphaned in the committee-members bucket
  • Subsequent create attempts for the same email+committeeUID find the stale key via UniqueMemberErrKeyExists → 409 Conflict, blocking the member from ever being created
  • Fix: use context.WithTimeout(context.Background(), 10s) in the rollback, matching the pattern already used by UpdateMember for its stale-key cleanup goroutine

Test plan

  • All existing tests pass (make test)
  • make lint clean
  • make build && make build-cli clean
  • Manual: verify a member blocked by a stale uniqueness key can be created after key removal

Closes LFXV2-2984

🤖 Generated with Claude Code

When an HTTP client times out mid-request (e.g. the
v1-sync-helper's 30 s http.Client.Timeout under onboarding
load), the committee-service request context is cancelled.
The deferred rollback in CreateMember was calling
deleteMemberKeys with that cancelled context, causing every
NATS KV cleanup call to fail immediately. The uniqueness key
written by UniqueMember (step 7) was left orphaned, blocking
future create attempts for the same email+committee pair with
a spurious 409 Conflict.

Fix: use context.WithTimeout(context.Background(), 10s) for
the rollback, matching the pattern already used by UpdateMember
for its stale-key cleanup goroutine.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Issue: LFXV2-2984
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Trevor Bramwell <tbramwell@linuxfoundation.org>
@bramwelt
bramwelt requested a review from a team as a code owner August 4, 2026 20:01
Copilot AI balanced review requested due to automatic review settings August 4, 2026 20:01

Copilot AI 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.

Pull request overview

Uses a detached, time-bounded context so CreateMember rollback can clean up NATS keys after request cancellation.

Changes:

  • Adds a 10-second rollback context.
  • Prevents orphaned uniqueness keys after client timeouts.

Comment thread internal/service/committee_member_writer.go
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

CreateMember rollback cleanup now uses a fresh background context with a 10-second timeout. Tests verify that member-key deletion continues after request cancellation.

Changes

Member creation cleanup

Layer / File(s) Summary
Use a bounded cleanup context
internal/service/committee_member_writer.go
The deferred rollback creates a 10-second timeout context before deleting tracked member keys.
Validate cleanup after cancellation
internal/service/committee_member_writer_test.go
The mock writer can reject cancelled contexts. A regression test verifies uniqueness-key deletion after rollback.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: using a detached context for CreateMember rollback cleanup.
Description check ✅ Passed The description accurately explains the cancelled-context bug, the detached-context fix, its impact, and the regression test.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/LFXV2-2984-rollback-detached-context

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
internal/service/committee_member_writer.go (1)

104-106: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Add a regression test for canceled request contexts.

The test at internal/service/committee_member_writer_test.go Lines [711]-[741] calls deleteMemberKeys directly with context.Background(). It does not prove that CreateMember completes rollback after the caller context is canceled.

Force a post-UniqueMember failure, cancel the caller context, and verify that all tracked keys are deleted.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/service/committee_member_writer.go` around lines 104 - 106, Add a
new regression test in committee_member_writer_test.go that verifies
CreateMember completes rollback cleanup even when the caller's context is
canceled. The test should set up a scenario that triggers a failure after the
UniqueMember check (to populate tracked keys), then cancel the caller context
passed to CreateMember, and finally assert that all tracked keys are deleted
despite the context cancellation. This ensures that the timeout-based cleanup
context created within CreateMember (as shown in the deleteMemberKeys call)
operates independently and successfully cleans up resources even when the
request context becomes canceled.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@internal/service/committee_member_writer.go`:
- Around line 104-106: Add a new regression test in
committee_member_writer_test.go that verifies CreateMember completes rollback
cleanup even when the caller's context is canceled. The test should set up a
scenario that triggers a failure after the UniqueMember check (to populate
tracked keys), then cancel the caller context passed to CreateMember, and
finally assert that all tracked keys are deleted despite the context
cancellation. This ensures that the timeout-based cleanup context created within
CreateMember (as shown in the deleteMemberKeys call) operates independently and
successfully cleans up resources even when the request context becomes canceled.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e65da023-1924-4d9f-8906-aa792040fd81

📥 Commits

Reviewing files that changed from the base of the PR and between 4747b11 and de6b5a5.

📒 Files selected for processing (1)
  • internal/service/committee_member_writer.go

Add TestCreateMember_RollbackSurvivesCancelledContext to prove the
LFXV2-2984 fix: rollback cleanup uses a detached context and succeeds
even when the request context is already cancelled.

- Add rejectCancelledCtx field to TestMockCommitteeMemberWriter so
  DeleteMember/GetMemberRevision return ctx.Err() when the context is
  done. Without this flag the existing mock ignores context state and
  the test would pass with either the old or new code.
- Pre-seed the uniqueness key in memberWriter.members so GetMemberRevision
  can resolve it during rollback (UniqueMember stores to w.keys but
  GetMemberRevision reads w.members).
- Cancel the caller context before calling CreateMember to simulate a
  timed-out HTTP client, force orgIndexErr to trigger rollback, and
  assert the uniqueness key appears in deletedKeys.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Issue: LFXV2-2984
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Trevor Bramwell <tbramwell@linuxfoundation.org>
Copilot AI review requested due to automatic review settings August 4, 2026 21:23
@bramwelt

bramwelt commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Review Feedback Addressed

Commit: e4d8727 — test(member): regression for cancelled-ctx rollback

Changes Made

  • internal/service/committee_member_writer_test.go: Added TestCreateMember_RollbackSurvivesCancelledContext — a regression test that cancels the caller context before CreateMember, forces a later write failure, and asserts the uniqueness key is cleaned up using a live (detached) cleanup context (per copilot-pull-request-reviewer[bot])

Threads Resolved

1 of 1 unresolved threads addressed in this iteration.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/service/committee_member_writer_test.go`:
- Around line 971-1033: Convert
TestCreateMember_RollbackSurvivesCancelledContext into a table-driven test using
a test-case slice and subtests, while retaining the existing internal
infrastructure mock fake setup and regression assertions. Keep the
cancelled-context scenario, injected org-index failure, rollback configuration,
and uniqueness-key deletion verification unchanged within the table-driven
structure.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 85d34be8-938d-463d-a740-a7df79a515f6

📥 Commits

Reviewing files that changed from the base of the PR and between de6b5a5 and e4d8727.

📒 Files selected for processing (1)
  • internal/service/committee_member_writer_test.go

Comment thread internal/service/committee_member_writer_test.go

Copilot AI 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.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

@bramwelt

bramwelt commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Review Feedback Addressed (iteration 2)

No new code changes — one thread responded to.

No Change Needed

  • internal/service/committee_member_writer_test.go:971-1033: The table-driven guideline applies to tests covering a range of input variations. This rollback regression test has exactly one meaningful scenario with per-test infrastructure state (rejectCancelledCtx, pre-seeded uniqueness key) that is specific to this failure path. The immediately adjacent TestCreateMember_OrgIndexWriteFailsRollsBack uses the same standalone pattern. Converting to a table with one row adds boilerplate without benefit (flagged by coderabbitai[bot])

Threads Resolved

0 of 1 thread resolved in this iteration — leaving open for reviewer confirmation.

@bramwelt
bramwelt merged commit 98b1cc6 into main Aug 5, 2026
11 checks passed
@bramwelt
bramwelt deleted the fix/LFXV2-2984-rollback-detached-context branch August 5, 2026 00:24
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.

3 participants