Skip to content

Open shifts follow-up: keep post keys across a reload, clear them on account deletion - #3566

Merged
gitar-bot[bot] merged 12 commits into
mainfrom
claude/project-thread-sr3mdk
Oct 16, 2026
Merged

gitar-bot[bot] merged 12 commits into
mainfrom
claude/project-thread-sr3mdk

Conversation

@BigSimmo

@BigSimmo BigSimmo commented Oct 15, 2026 •

Copy link
Copy Markdown
Owner

User description

Summary

Before: the post key that stops a retried gap post creating a second open shift lived only in page memory. If the answer was lost and the person reloaded the page before retrying, a new key was made and a duplicate shift could still be posted. Saved post keys also kept naming a person after their account was deleted.

After: the post page keeps each date's key in session storage for up to 24 hours, so a retry after a reload sends the same key. Signing in as someone else clears the stored keys. In the database, roster_open_post_keys.posted_by now references auth.users with on delete set null, so deleting an account clears the name from its post keys.

How: new migration 20261015181500_roster_open_post_keys_posted_by_fk.sql nulls any posted_by with no matching user, then adds the foreign key. schema.sql, the drift manifest and the migration seal are updated (the seal adds one new entry only). New open-shifts-post-keys.ts handles storage with every call guarded. The roster alerts test now checks the exact assignment read for a new post, and the SQL behaviour test proves account deletion clears the key's name.

Areas touched: Personal practice, Data platform and access

RAG impact: no retrieval behaviour change, roster only.

Verification

  • check:migration-immutability: "312 shipped migrations unchanged".
  • Vitest: open-shifts post retry, roster alerts and roster team API tests, 38 passed. Typecheck and eslint clean on the changed files (earlier run), prettier clean.
  • Not run locally: the Postgres 17 drift manifest regeneration and browser tests. CI's migration replay and drift freshness checks are the proof.

Risk and rollout

  • Database migration. Merging applies it to the live database within seconds. Merge only inside the approved window, with the owner's named yes to this specific change. Keep this PR as a draft until then.
  • Rollback: a forward migration dropping roster_open_post_keys_posted_by_fkey.

Notes

  • No patient data. Work mode roster data only.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LuYbdEGXeMntVA9KMgtAEx

Generated by Claude Code


Summary by cubic

Keeps open-shift post keys in session storage so a retry after a reload never creates a duplicate shift, clears those keys on account change or deletion, and stops a post when the signed-in account changes mid-run.

  • Post keys survive reloads for up to 24 hours and are dropped once the post is confirmed, either by the server's answer or by the refreshed list showing the shift went live.
  • Keys are tied to the signed-in person; signing out clears them, another account never reuses them, and a mid-post account change stops further requests while keeping the dates that already went live and naming them in the stop message.
  • A sealed database migration makes roster_open_post_keys.posted_by reference auth.users with on delete set null, matching roster_open_shifts.posted_by; the seal hash is added to .gitleaksignore since the filename contains "keys" and the value reads as a key.
  • open-shifts-post-keys.ts guards all storage access so the page still works when storage is blocked.

Written for commit 9755975. Summary will update on new commits.

View guided diff Turn on auto-fix

Summary by CodeRabbit

  • Bug Fixes
    • Open-shift posts now retain retry keys across page reloads, helping prevent duplicate posts when a submission gets no response. Keys are cleared once the post is confirmed.
    • Retry keys are cleared when switching accounts, and related records are handled safely when an account is deleted.
    • If the signed-in account changes while a post is in progress, further requests stop and the results aren’t applied.

CodeAnt-AI Description

Keep open-shift retry keys across reloads and clear them when no longer needed

What Changed

  • Retrying an open-shift post after a page reload reuses its saved key for up to 24 hours, preventing a lost response from creating a duplicate shift.
  • Keys are cleared when a post is confirmed and are not reused by another person signing in on the same tab.
  • Deleting an account clears that person’s ID from their saved post keys.

Impact

✅ Fewer duplicate open shifts after reloads
✅ Safe retries after lost post responses
✅ No deleted-account IDs on saved post keys

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

…account deletion

- Post keys are kept in the tab's session storage, so a retry after a reload sends the
  same key and the server answers with the shift it already saved.
- roster_open_post_keys.posted_by now references auth.users on delete set null, like
  roster_open_shifts.posted_by (migration 20261015181500, not yet sealed).
- Account-deletion SQL test covers the post keys. Roster alerts test checks the new post
  takes the offer path.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LuYbdEGXeMntVA9KMgtAEx
Adds the new migration's hash only. No existing entry changes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LuYbdEGXeMntVA9KMgtAEx
@coderabbitai

coderabbitai Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Warning

Review limit reached

Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab.

Next included review available in 16 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available. Your 97 included PR review attempts over the past 7 days set your current allowance at 1 review per hour.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Repository: BigSimmo/PsychSift/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Team
  • Run ID: 6e4d0c66-b4d6-4527-a101-fd991bc1a5af
📥 Commits

Reviewing files that changed from the base of the PR and between 2444c3b and 9755975.

📒 Files selected for processing: 11
  • .gitleaksignore
  • src/components/open-shifts/open-shifts-post-keys.ts
  • src/components/open-shifts/open-shifts-post-page.tsx
  • src/lib/account-scoped-browser-state.ts
  • supabase/applied-migration-hashes.json
  • supabase/drift-manifest.json
  • supabase/migrations/20261015181500_roster_open_post_keys_posted_by_fk.sql
  • supabase/schema.sql
  • tests/open-shifts-post-retry.dom.test.tsx
  • tests/roster-alerts.test.ts
  • tests/sql/roster-behaviour.sql
📝 Walkthrough

Open-shift post keys now persist in session storage across reloads and are removed after a successful post. Account transitions clear the stored keys. A database migration adds an account reference constraint for posted-by values. The Gitleaks ignore list also records a historical migration-hash finding.

Changes: Open-shift post keys

Layer / File(s) Summary
Persist and reuse retry keys
src/components/open-shifts/open-shifts-post-keys.ts, src/components/open-shifts/open-shifts-post-page.tsx, src/lib/account-scoped-browser-state.ts, tests/open-shifts-post-retry.dom.test.tsx, tests/roster-alerts.test.ts
The post page gets keys from session storage and removes them after success. Account transitions clear the stored keys. Tests cover reload-and-retry behavior and the assignment range read for a gap post.
Constrain poster account references
supabase/migrations/20261015181500_roster_open_post_keys_posted_by_fk.sql, supabase/schema.sql, supabase/drift-manifest.json, supabase/applied-migration-hashes.json, tests/sql/roster-behaviour.sql
The migration clears invalid posted_by values, adds an index, and adds a foreign key to auth.users(id) with ON DELETE SET NULL. The schema, manifests, and account-deletion test record or verify the constraint.

Secret scan allowlist

Layer / File(s) Summary
Allowlist migration hash finding
.gitleaksignore
Adds an allowlist entry and comments for a historical migration-hash finding identified as a filename-triggered match.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant OpenShiftsPostPage
  participant postKeyFor
  participant sessionStorage
  participant PostEndpoint
  OpenShiftsPostPage->>postKeyFor: Request key for shift signature
  postKeyFor->>sessionStorage: Read or save retry key
  sessionStorage-->>postKeyFor: Stored key, if present
  postKeyFor-->>OpenShiftsPostPage: Return retry key
  OpenShiftsPostPage->>PostEndpoint: Submit shift with retry key
  PostEndpoint-->>OpenShiftsPostPage: Return saved shift
  OpenShiftsPostPage->>forgetPostKey: Remove key after success
  forgetPostKey->>sessionStorage: Delete stored key
Loading

Suggested reviewers: claude

Merge Risk: 🟡 Moderate · up to 2444c

Correct the manifest and seal before merging, and confirm the migration’s write-blocking impact for the approved production window.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 8 functions across 5 files. (6 skipped: 6… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title clearly identifies the two primary changes: preserving open-shift post keys across reloads and clearing them on account deletion.
Description check Passed The description covers the summary, affected areas, verification results, known test gaps, migration risk, rollback, production effect, and notes. It is sufficiently complete for the repository templa…
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.
Full details: Docstring Coverage

Explanation

Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 8 functions across 5 files. (6 skipped: 6 unsupported.)

✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

The filename contains "keys" and its sha256 value matches the
generic-api-key rule. It is a file hash, not a secret.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LuYbdEGXeMntVA9KMgtAEx
@codeant-ai

codeant-ai Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

🏁 CodeAnt Quality Gate Results

Commit: 97559753
Scan Time: 2026-10-16 08:22:55 UTC

✅ Overall Status: PASSED

Quality Gate Details

Quality Gate Status Details
Secrets ✅ PASSED 0 secrets found
Duplicate Code ✅ PASSED 0.0% duplicated
SAST ✅ PASSED No security issues
Bugs ✅ PASSED Rating S: No bugs
IAC ✅ PASSED No IAC issues

View Full Results

@supabase

supabase Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Updates to Preview Branch (claude/project-thread-sr3mdk) ↗︎

Deployments Status Updated
Database ✅ Fri, 16 Oct 2026 07:56:54 UTC
Services ✅ Fri, 16 Oct 2026 07:56:54 UTC
APIs ✅ Fri, 16 Oct 2026 07:56:54 UTC

Tasks are run on every commit but only new migration files are pushed.
Close and reopen this PR if you want to apply changes from existing seed or migration files.

Tasks Status Updated
Configurations ✅ Fri, 16 Oct 2026 07:56:56 UTC
Migrations ✅ Fri, 16 Oct 2026 07:56:57 UTC
Seeding ✅ Fri, 16 Oct 2026 07:56:59 UTC
Edge Functions ✅ Fri, 16 Oct 2026 07:57:02 UTC

View logs for this Workflow Run ↗︎.
Learn more about Supabase for Git ↗︎.

gitar-bot[bot]
gitar-bot Bot previously approved these changes Oct 15, 2026

@gitar-bot gitar-bot 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.

Gitar has auto-approved this PR (configure)

@supabase

supabase Bot commented Oct 15, 2026

Copy link
Copy Markdown

Updates to Preview Branch (claude/project-thread-sr3mdk) ↗︎

Deployments Status Updated
Database 🔄 Thu, 15 Oct 2026 23:34:05 GMT
Services 🔄 Thu, 15 Oct 2026 23:34:05 GMT
APIs 🔄 Thu, 15 Oct 2026 23:34:05 GMT

Tasks are run on every commit but only new migration files are pushed.
Close and reopen this PR if you want to apply changes from existing seed or migration files.

Tasks Status Updated
Configurations 🔄 Thu, 15 Oct 2026 23:34:05 GMT
Migrations 🔄 Thu, 15 Oct 2026 23:34:05 GMT
Seeding 🔄 Thu, 15 Oct 2026 23:34:05 GMT
Edge Functions 🔄 Thu, 15 Oct 2026 23:34:05 GMT

Preview Branch Database Settings ↗︎.
Learn more about Supabase Branching ↗︎.

@BigSimmo
BigSimmo marked this pull request as ready for review October 16, 2026 05:12

@greptile-apps greptile-apps 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.

BigSimmo has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-16T07:07:11.483999Z 8d5fc36 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@codeant-ai

codeant-ai Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

🤖 CodeAnt AI — Review Status

Status Commit Started (UTC) Finished (UTC)
✅ Incremental review completed 8d5fc36 Oct 16, 2026 · 07:03 07:07
✅ Reviewed your PR 5cb9dad Oct 16, 2026 · 05:12 05:19

@codeant-ai

codeant-ai Bot commented Oct 16, 2026

Copy link
Copy Markdown

Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@codeant-ai codeant-ai Bot added the size:L This PR changes 100-499 lines, ignoring generated files label Oct 16, 2026
@qodo-code-review

qodo-code-review Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

PR Summary by Qodo

Preserve open-shift retry keys across reloads and clear deleted posters

🐞 Bug fix 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Keep open-shift retry keys for 24 hours in the current tab to prevent duplicate posts after
 reloads.
• Clear confirmed keys and account-scoped browser state so later posts cannot reuse them.
• Null deleted users’ IDs in database post keys, with migration and retry coverage.
Diagram

graph TD
  Page["Post page"] -->|"reuse or forget"| Session[("Session keys")] -->|"retry key"| Action["Post action"] --> Once["Post once"] --> Database[("Post keys")]
  Users["Auth users"] -->|"deletion nulls poster"| Database
Loading
High-Level Assessment

The approach fits the existing server-side idempotency mechanism: session storage covers reloads without retaining keys across tabs or browser sessions, while the foreign key gives deleted accounts database-enforced cleanup. Longer-lived local storage would add unnecessary account-state exposure.

Files changed (11) +182 / -9

Bug fix (4) +99 / -6
open-shifts-post-keys.tsPersist and retire tab-scoped post keys +63/-0

Persist and retire tab-scoped post keys

• Provides guarded session-storage access for per-shift retry keys, retaining valid keys for up to 24 hours. It also forgets keys when a post is confirmed and falls back to in-memory keys when storage is unavailable.

src/components/open-shifts/open-shifts-post-keys.ts

open-shifts-post-page.tsxReuse post keys after reload and forget confirmed keys +11/-6

Reuse post keys after reload and forget confirmed keys

• Uses account-qualified shift signatures to retrieve retry keys before posting. Removes a key when either the post response or the refreshed shift list confirms the post.

src/components/open-shifts/open-shifts-post-page.tsx

account-scoped-browser-state.tsClear post keys during account transitions +8/-0

Clear post keys during account transitions

• Registers the new session-storage key with account-scoped browser state and removes it during the existing account cleanup.

src/lib/account-scoped-browser-state.ts

20261015181500_roster_open_post_keys_posted_by_fk.sqlClear orphaned posters and constrain post-key ownership +17/-0

Clear orphaned posters and constrain post-key ownership

• Nulls existing poster IDs without matching users, then adds a foreign key that nulls posted_by when an account is deleted. The migration sets lock and statement timeouts.

supabase/migrations/20261015181500_roster_open_post_keys_posted_by_fk.sql

Tests (3) +67 / -0
open-shifts-post-retry.dom.test.tsxCover reload retries and account-separated keys +55/-0

Cover reload retries and account-separated keys

• Verifies that a retry after remounting sends the original key, another signed-in actor receives a different key, and confirmed posts clear stored keys.

tests/open-shifts-post-retry.dom.test.tsx

roster-alerts.test.tsAssert new posts take the offer-alert path +5/-0

Assert new posts take the offer-alert path

• Checks that alert dispatch reads the expected assignment date range when finding recipients for a newly posted gap.

tests/roster-alerts.test.ts

roster-behaviour.sqlVerify account deletion clears post-key poster IDs +7/-0

Verify account deletion clears post-key poster IDs

• Creates a post-key fixture naming the account under test, then asserts that deleting the user leaves no post key naming them.

tests/sql/roster-behaviour.sql

Other (4) +16 / -3
.gitleaksignoreExclude the migration seal’s false-positive hash +3/-0

Exclude the migration seal’s false-positive hash

• Ignores the new migration hash entry flagged as an API key because its filename contains “keys.” The ignored value is a SHA-256 migration seal, not a secret.

.gitleaksignore

applied-migration-hashes.jsonSeal the new post-key migration +3/-2

Seal the new post-key migration

• Adds the new migration’s SHA-256 entry and updates the seal timestamp without changing existing migration hashes.

supabase/applied-migration-hashes.json

drift-manifest.jsonRecord the post-key foreign key in the drift manifest +6/-1

Record the post-key foreign key in the drift manifest

• Updates the schema hash and constraint snapshot to include the reference to auth.users with ON DELETE SET NULL.

supabase/drift-manifest.json

schema.sqlReflect post-key account deletion behavior in schema +4/-0

Reflect post-key account deletion behavior in schema

• Adds the roster_open_post_keys.posted_by foreign key with ON DELETE SET NULL to the schema snapshot.

supabase/schema.sql

Copy link
Copy Markdown
Owner Author

PR mergeability is red because this branch conflicts with main in supabase/applied-migration-hashes.json and supabase/drift-manifest.json. The conflict comes from #3560 landing 20261015180000_prefilter_text_only_governed_retrieval.sql. Resolving it adds this PR's seal line after that entry, and no existing entry changes. It also takes main's manifest and recomputes schema_sha256 over the merged schema.sql. Migration order is fine (181500 sorts after 180000).

The fix is held for the owner's specific yes, because it edits the migration seal file. No re-run is needed: the check passes once the merge is pushed.


Generated by Claude Code

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5cb9dadbad

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@qodo-code-review

qodo-code-review Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

No findings for this group


Remediation recommended

No findings for this group


Informational

No findings for this group

Grey Divider


Resolved findings

1. Account switches still post later dates ✓ Resolved
Description
post() guards each subsequent date with actorIdRef, which updates only after the roster-derived
actor ID changes in a React effect; the account-transition listener clears post keys but does not
invalidate that ref. If accounts switch while a date request is pending, the next date can pass the
stale guard and submit the previously selected team under the new account’s authenticated session.
Code

src/components/open-shifts/open-shifts-post-page.tsx[R260-264]

+      // Do not issue another request after the account changes while a previous date is pending.
+      if (actorIdRef.current !== postingActorId) {
+        stopped = true;
+        break;
+      }
Relevance

●●● Strong

Recent account-switch precedents favor synchronous invalidation of stale identity state during
pending requests.

PR-#3563
PR-#3363

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The authentication callback clears account-scoped state and synchronously dispatches a transition
event before publishing the new session, but the page’s listener clears post keys without updating
actorIdRef. That ref changes only through an effect tied to the roster-derived actor ID, and a
session change alone does not reload the cached roster response. Because the next-request guard
reads the ref while the API obtains the actor from the request’s authenticated session rather than
the captured actor ID, a pending request can resolve after the transition and allow another date to
be sent under the new session.

src/components/open-shifts/open-shifts-post-page.tsx[119-134]
src/components/open-shifts/open-shifts-post-page.tsx[240-264]
src/components/open-shifts/use-posted-shifts.ts[87-120]
src/components/roster/use-roster-team.ts[80-112]
src/lib/supabase/client.tsx[432-439]
src/lib/supabase/client.tsx[409-428]
src/lib/account-scoped-browser-state.ts[314-320]
src/components/open-shifts/open-shifts-post-page.tsx[258-264]
src/components/roster/use-roster-team.ts[208-229]
src/app/api/roster/team/[serviceId]/route.ts[79-82]
Skill: access
Skill: frontend-architecture-review

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The in-flight multi-date post guard relies on a roster-derived actor ID that can remain stale across an authentication transition. A pending run can therefore send a later date under the new session with the previous account’s selected team.

## Fix Focus Areas
- src/components/open-shifts/open-shifts-post-page.tsx[119-134]
- src/components/open-shifts/open-shifts-post-page.tsx[241-264]
- tests/open-shifts-post-retry.dom.test.tsx[272-299]

## Recommended Fix
Track an account-transition generation in a ref and increment it synchronously in the existing transition listener. Capture the generation when `post()` starts and stop before each subsequent request if it has changed, independently of the cached roster actor ID or when the effect updates `actorIdRef`. Add a test that fires the actual transition while the first request is pending, before changing the mocked roster actor, and asserts that no second request is made.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Account deletion scans every post key ✓ Resolved
Description
The new posted_by foreign key has no supporting index on roster_open_post_keys; the table's
existing indexes start with service_id and open_shift_id. Deleting an account must locate rows
for ON DELETE SET NULL, so the lookup scans the accumulating post-key table and can slow or block
deletion as it grows.
Code

supabase/migrations/20261015181500_roster_open_post_keys_posted_by_fk.sql[R15-17]

+alter table public.roster_open_post_keys
+  add constraint roster_open_post_keys_posted_by_fkey
+  foreign key (posted_by) references auth.users (id) on delete set null;
Relevance

●●● Strong

Foreign-key integrity and migration/schema correctness findings are routinely accepted; indexing
prevents scalable account-deletion scans.

PR-#2187

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The Supabase Postgres best-practices skill explicitly instructs reviewers to index foreign-key
columns so parent deletions do not scan the referencing table. This migration adds the foreign key,
while the table definition shows indexes only on (service_id, post_key) and open_shift_id.

supabase/migrations/20261015181500_roster_open_post_keys_posted_by_fk.sql[15-17]
supabase/schema.sql[34198-34209]
Skill: supabase-postgres-best-practices

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Account deletion must find post keys by `posted_by`, but the newly constrained column has no supporting index.
## Fix Focus Areas
- supabase/migrations/20261015181500_roster_open_post_keys_posted_by_fk.sql[15-17]
- supabase/schema.sql[34205-34209]
## Recommended Fix
Add an index with `posted_by` as its leading column in the migration and schema snapshot, update the drift manifest through the repository's approved workflow, and verify account deletion against the indexed schema.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. A new user can inherit an old post key ✓ Resolved
Description
postKeyFor reuses post keys by shift signature without verifying their account owner: it prefers
the mounted page’s memory[signature] after session-storage cleanup and can also read a stale
stored key when a full-page authentication callback bypasses that cleanup. If a different account
posts an identical shift in the same tab, the server rejects the previous poster’s key as
roster_conflict, and restoring that key to storage can make the conflict recur after a reload.
Code

src/components/open-shifts/open-shifts-post-keys.ts[R49-51]

+  const key = memory[signature] ?? stored[signature]?.key ?? crypto.randomUUID();
+  memory[signature] = key;
+  if (stored[signature]?.key !== key) write({ ...stored, [signature]: { key, at: now } });
Relevance

●●● Strong

Accepted ownership-boundary bugs in retry reconciliation; stale cross-account idempotency keys
directly violate the PR’s account-transition intent.

PR-#3483

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The account-transition cleanup removes session-stored keys but leaves the mounted page’s post-key
ref intact, which postKeyFor checks first and writes back to storage. A full-page authentication
callback can instead load the new session as the provider’s initial identity without running
account-change cleanup, leaving the tab’s stored key available to the helper. In either path, the
server rejects a key already used by a different poster.

src/components/open-shifts/open-shifts-post-keys.ts[47-52]
src/lib/account-scoped-browser-state.ts[312-315]
src/components/open-shifts/open-shifts-post-page.tsx[113-117]
supabase/migrations/20261015174500_roster_open_post_once.sql[51-57]
src/app/auth/callback/route.ts[108-121]
src/lib/supabase/client.tsx[355-379]
src/lib/supabase/client.tsx[405-431]
src/components/open-shifts/open-shifts-post-keys.ts[18-29]
src/components/open-shifts/open-shifts-post-keys.ts[46-52]
supabase/migrations/20261015174500_roster_open_post_once.sql[37-53]
Skill: privacy

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Post keys are reused without checking their account owner. A mounted page can restore a cleared key from memory, while a full-page authentication callback can leave a previous account’s stored key in place; either path can make another account’s matching post conflict.

## Fix Focus Areas
- src/components/open-shifts/open-shifts-post-page.tsx[113-117]
- src/components/open-shifts/open-shifts-post-page.tsx[261-265]
- src/components/open-shifts/open-shifts-post-keys.ts[18-52]
- src/lib/account-scoped-browser-state.ts[312-315]

## Recommended Fix
Store and validate the authenticated account ID alongside each persisted key before reuse, discarding keys owned by another account while preserving same-account keys across ordinary reloads. Subscribe the mounted post page to account transitions and clear its post-key ref when one occurs. Add a focused test that switches accounts without unmounting, retries a matching post, and confirms both the submitted and stored keys are new.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View resolved (2)
4. The next account cannot post the same date ✗ Dismissed
Description
The stopped path calls setDone(nowDone) after the account changes, but done is keyed by shift
signature rather than poster. When the previous account’s pending request succeeds, the newly
signed-in manager sees that date as already posted and the page excludes it from subsequent posts,
even if both managers can post for the team.
Code

src/components/open-shifts/open-shifts-post-page.tsx[R321-323]

+    // Keep what this run learned even when it stopped, so a date that already went live is never sent again.
    setDone(nowDone);
    setUnanswered(nowUnanswered);
Relevance

●●● Strong

Completed-date state is not actor-scoped, matching accepted precedents requiring account-owned state
invalidation on switches.

PR-#3563
PR-#3304

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The stopped path now persists nowDone after an actor change. Successful requests add dates under
signature(on), while alreadyPosted and toPost consult that same signature without checking the
actor. The rendered form labels those dates already posted and disables submission when none remain;
the account-scoped shifts hook does not reset this page-local state.

src/components/open-shifts/open-shifts-post-page.tsx[184-190]
src/components/open-shifts/open-shifts-post-page.tsx[224-226]
src/components/open-shifts/open-shifts-post-page.tsx[301-328]
src/components/open-shifts/open-shifts-post-page.tsx[545-568]
src/components/open-shifts/use-posted-shifts.ts[139-165]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A successful request from the previous account is saved into page-level `done` state after a switch. That state then prevents the new manager from posting an identical date.
## Fix Focus Areas
- src/components/open-shifts/open-shifts-post-page.tsx[119-134]
- src/components/open-shifts/open-shifts-post-page.tsx[321-328]
## Recommended Fix
Associate completed and unanswered dates with the actor that made each attempt. On account changes, use only the current actor’s entries to determine which dates can be posted, while preserving the previous actor’s results for a later return to that account. Test the stopped run from both accounts.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. A switched account can duplicate a shift ✓ Resolved
Description
keyFor captures the render's state.actorId, so an ongoing post() continues saving keys under
the previous account's prefix after authentication changes. If the next date is sent under the new
account and its answer is lost, reloading looks under the new account's prefix, sends a different
key, and can create a second opening.
Code

src/components/open-shifts/open-shifts-post-page.tsx[119]

+  const keyFor = (on: string) => `${state.actorId ?? ""}|${signature(on)}`;
Relevance

●●● Strong

Recent account-switch precedents accept invalidating stale async work and scoping state to the
current identity.

PR-#3363
PR-#3563

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The post loop awaits each request before obtaining the next date's key, but keyFor belongs to the
render that started the loop. Auth transitions clear stored keys without cancelling that loop;
subsequent requests use the current session, while the server deduplicates only matching post keys.
A reload under the new actor therefore cannot retrieve a key saved under the old actor's prefix.

src/components/open-shifts/open-shifts-post-page.tsx[117-119]
src/components/open-shifts/open-shifts-post-page.tsx[243-279]
src/lib/supabase/client.tsx[417-434]
src/components/roster/use-roster-team.ts[205-229]
supabase/migrations/20261015174500_roster_open_post_once.sql[47-62]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
An in-progress multi-date post can continue after an account switch, saving a retry key under the old actor's prefix for a request made as the new actor.
## Fix Focus Areas
- src/components/open-shifts/open-shifts-post-page.tsx[118-119]
- src/components/open-shifts/open-shifts-post-page.tsx[243-283]
## Recommended Fix
Capture the posting actor at the start and stop processing further dates if the current authenticated actor changes between requests. Ensure a stopped post does not persist a key for a request made under a different account, and test an account switch while the first date's request is pending.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
✅ Compliance rules (platform): 9 rules
✅ Cross-repo context — repo relationships
Review mode: Auto: ⚖️ Balanced: Authentication transition changes affect in-progress posting behavior.
✅ Fix-loop round: fix loop

Grey Divider

Tip of the day
💡 Did you know, you can pick a display mode instead of tweaking every Display option by hand

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Previous reviews

Review updated until commit 9755975 ⚖️ Balanced

Results up to commit 5cb9dad ⚖️ Balanced


🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)


Action required

No findings for this group


Remediation recommended

No findings for this group


Informational

No findings for this group


Resolved findings

1. Account deletion scans every post key ✓ Resolved
Description
The new posted_by foreign key has no supporting index on roster_open_post_keys; the table's
existing indexes start with service_id and open_shift_id. Deleting an account must locate rows
for ON DELETE SET NULL, so the lookup scans the accumulating post-key table and can slow or block
deletion as it grows.
Code

supabase/migrations/20261015181500_roster_open_post_keys_posted_by_fk.sql[R15-17]

+alter table public.roster_open_post_keys
+  add constraint roster_open_post_keys_posted_by_fkey
+  foreign key (posted_by) references auth.users (id) on delete set null;
Relevance

●●● Strong

Foreign-key integrity and migration/schema correctness findings are routinely accepted; indexing
prevents scalable account-deletion scans.

PR-#2187

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The Supabase Postgres best-practices skill explicitly instructs reviewers to index foreign-key
columns so parent deletions do not scan the referencing table. This migration adds the foreign key,
while the table definition shows indexes only on (service_id, post_key) and open_shift_id.

supabase/migrations/20261015181500_roster_open_post_keys_posted_by_fk.sql[15-17]
supabase/schema.sql[34198-34209]
Skill: supabase-postgres-best-practices

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Account deletion must find post keys by `posted_by`, but the newly constrained column has no supporting index.
## Fix Focus Areas
- supabase/migrations/20261015181500_roster_open_post_keys_posted_by_fk.sql[15-17]
- supabase/schema.sql[34205-34209]
## Recommended Fix
Add an index with `posted_by` as its leading column in the migration and schema snapshot, update the drift manifest through the repository's approved workflow, and verify account deletion against the indexed schema.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. A new user can inherit an old post key ✓ Resolved
Description
postKeyFor reuses post keys by shift signature without verifying their account owner: it prefers
the mounted page’s memory[signature] after session-storage cleanup and can also read a stale
stored key when a full-page authentication callback bypasses that cleanup. If a different account
posts an identical shift in the same tab, the server rejects the previous poster’s key as
roster_conflict, and restoring that key to storage can make the conflict recur after a reload.
Code

src/components/open-shifts/open-shifts-post-keys.ts[R49-51]

+  const key = memory[signature] ?? stored[signature]?.key ?? crypto.randomUUID();
+  memory[signature] = key;
+  if (stored[signature]?.key !== key) write({ ...stored, [signature]: { key, at: now } });
Relevance

●●● Strong

Accepted ownership-boundary bugs in retry reconciliation; stale cross-account idempotency keys
directly violate the PR’s account-transition intent.

PR-#3483

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The account-transition cleanup removes session-stored keys but leaves the mounted page’s post-key
ref intact, which postKeyFor checks first and writes back to storage. A full-page authentication
callback can instead load the new session as the provider’s initial identity without running
account-change cleanup, leaving the tab’s stored key available to the helper. In either path, the
server rejects a key already used by a different poster.

src/components/open-shifts/open-shifts-post-keys.ts[47-52]
src/lib/account-scoped-browser-state.ts[312-315]
src/components/open-shifts/open-shifts-post-page.tsx[113-117]
supabase/migrations/20261015174500_roster_open_post_once.sql[51-57]
src/app/auth/callback/route.ts[108-121]
src/lib/supabase/client.tsx[355-379]
src/lib/supabase/client.tsx[405-431]
src/components/open-shifts/open-shifts-post-keys.ts[18-29]
src/components/open-shifts/open-shifts-post-keys.ts[46-52]
supabase/migrations/20261015174500_roster_open_post_once.sql[37-53]
Skill: privacy

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Post keys are reused without checking their account owner. A mounted page can restore a cleared key from memory, while a full-page authentication callback can leave a previous account’s stored key in place; either path can make another account’s matching post conflict.

## Fix Focus Areas
- src/components/open-shifts/open-shifts-post-page.tsx[113-117]
- src/components/open-shifts/open-shifts-post-page.tsx[261-265]
- src/components/open-shifts/open-shifts-post-keys.ts[18-52]
- src/lib/account-scoped-browser-state.ts[312-315]

## Recommended Fix
Store and validate the authenticated account ID alongside each persisted key before reuse, discarding keys owned by another account while preserving same-account keys across ordinary reloads. Subscribe the mounted post page to account transitions and clear its post-key ref when one occurs. Add a focused test that switches accounts without unmounting, retries a matching post, and confirms both the submitted and stored keys are new.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Results up to commit 8d5fc36 ⚖️ Balanced


🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)


Action required

No findings for this group


Remediation recommended

No findings for this group


Informational

No findings for this group


Resolved findings

1. A switched account can duplicate a shift ✓ Resolved
Description
keyFor captures the render's state.actorId, so an ongoing post() continues saving keys under
the previous account's prefix after authentication changes. If the next date is sent under the new
account and its answer is lost, reloading looks under the new account's prefix, sends a different
key, and can create a second opening.
Code

src/components/open-shifts/open-shifts-post-page.tsx[119]

+  const keyFor = (on: string) => `${state.actorId ?? ""}|${signature(on)}`;
Relevance

●●● Strong

Recent account-switch precedents accept invalidating stale async work and scoping state to the
current identity.

PR-#3363
PR-#3563

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The post loop awaits each request before obtaining the next date's key, but keyFor belongs to the
render that started the loop. Auth transitions clear stored keys without cancelling that loop;
subsequent requests use the current session, while the server deduplicates only matching post keys.
A reload under the new actor therefore cannot retrieve a key saved under the old actor's prefix.

src/components/open-shifts/open-shifts-post-page.tsx[117-119]
src/components/open-shifts/open-shifts-post-page.tsx[243-279]
src/lib/supabase/client.tsx[417-434]
src/components/roster/use-roster-team.ts[205-229]
supabase/migrations/20261015174500_roster_open_post_once.sql[47-62]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
An in-progress multi-date post can continue after an account switch, saving a retry key under the old actor's prefix for a request made as the new actor.
## Fix Focus Areas
- src/components/open-shifts/open-shifts-post-page.tsx[118-119]
- src/components/open-shifts/open-shifts-post-page.tsx[243-283]
## Recommended Fix
Capture the posting actor at the start and stop processing further dates if the current authenticated actor changes between requests. Ensure a stopped post does not persist a key for a request made under a different account, and test an account switch while the first date's request is pending.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Results up to commit ef8884a ⚖️ Balanced


🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)


Action required

No findings for this group


Remediation recommended

No findings for this group


Informational

No findings for this group


Resolved findings

1. Account switches still post later dates ✓ Resolved
Description
post() guards each subsequent date with actorIdRef, which updates only after the roster-derived
actor ID changes in a React effect; the account-transition listener clears post keys but does not
invalidate that ref. If accounts switch while a date request is pending, the next date can pass the
stale guard and submit the previously selected team under the new account’s authenticated session.
Code

src/components/open-shifts/open-shifts-post-page.tsx[R260-264]

+      // Do not issue another request after the account changes while a previous date is pending.
+      if (actorIdRef.current !== postingActorId) {
+        stopped = true;
+        break;
+      }
Relevance

●●● Strong

Recent account-switch precedents favor synchronous invalidation of stale identity state during
pending requests.

PR-#3563
PR-#3363

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The authentication callback clears account-scoped state and synchronously dispatches a transition
event before publishing the new session, but the page’s listener clears post keys without updating
actorIdRef. That ref changes only through an effect tied to the roster-derived actor ID, and a
session change alone does not reload the cached roster response. Because the next-request guard
reads the ref while the API obtains the actor from the request’s authenticated session rather than
the captured actor ID, a pending request can resolve after the transition and allow another date to
be sent under the new session.

src/components/open-shifts/open-shifts-post-page.tsx[119-134]
src/components/open-shifts/open-shifts-post-page.tsx[240-264]
src/components/open-shifts/use-posted-shifts.ts[87-120]
src/components/roster/use-roster-team.ts[80-112]
src/lib/supabase/client.tsx[432-439]
src/lib/supabase/client.tsx[409-428]
src/lib/account-scoped-browser-state.ts[314-320]
src/components/open-shifts/open-shifts-post-page.tsx[258-264]
src/components/roster/use-roster-team.ts[208-229]
src/app/api/roster/team/[serviceId]/route.ts[79-82]
Skill: access
Skill: frontend-architecture-review

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The in-flight multi-date post guard relies on a roster-derived actor ID that can remain stale across an authentication transition. A pending run can therefore send a later date under the new session with the previous account’s selected team.

## Fix Focus Areas
- src/components/open-shifts/open-shifts-post-page.tsx[119-134]
- src/components/open-shifts/open-shifts-post-page.tsx[241-264]
- tests/open-shifts-post-retry.dom.test.tsx[272-299]

## Recommended Fix
Track an account-transition generation in a ref and increment it synchronously in the existing transition listener. Capture the generation when `post()` starts and stop before each subsequent request if it has changed, independently of the cached roster actor ID or when the effect updates `actorIdRef`. Add a test that fires the actual transition while the first request is pending, before changing the mocked roster actor, and asserts that no second request is made.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. The next account cannot post the same date ✗ Dismissed
Description
The stopped path calls setDone(nowDone) after the account changes, but done is keyed by shift
signature rather than poster. When the previous account’s pending request succeeds, the newly
signed-in manager sees that date as already posted and the page excludes it from subsequent posts,
even if both managers can post for the team.
Code

src/components/open-shifts/open-shifts-post-page.tsx[R321-323]

+    // Keep what this run learned even when it stopped, so a date that already went live is never sent again.
    setDone(nowDone);
    setUnanswered(nowUnanswered);
Relevance

●●● Strong

Completed-date state is not actor-scoped, matching accepted precedents requiring account-owned state
invalidation on switches.

PR-#3563
PR-#3304

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The stopped path now persists nowDone after an actor change. Successful requests add dates under
signature(on), while alreadyPosted and toPost consult that same signature without checking the
actor. The rendered form labels those dates already posted and disables submission when none remain;
the account-scoped shifts hook does not reset this page-local state.

src/components/open-shifts/open-shifts-post-page.tsx[184-190]
src/components/open-shifts/open-shifts-post-page.tsx[224-226]
src/components/open-shifts/open-shifts-post-page.tsx[301-328]
src/components/open-shifts/open-shifts-post-page.tsx[545-568]
src/components/open-shifts/use-posted-shifts.ts[139-165]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A successful request from the previous account is saved into page-level `done` state after a switch. That state then prevents the new manager from posting an identical date.
## Fix Focus Areas
- src/components/open-shifts/open-shifts-post-page.tsx[119-134]
- src/components/open-shifts/open-shifts-post-page.tsx[321-328]
## Recommended Fix
Associate completed and unanswered dates with the actor that made each attempt. On account changes, use only the current actor’s entries to determine which dates can be posted, while preserving the previous actor’s results for a later return to that account. Test the stopped run from both accounts.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

@cubic-dev-ai

cubic-dev-ai Bot commented Oct 16, 2026

Copy link
Copy Markdown
Contributor

Auto-fix isn't included in your plan ↗

Upgrade to Team

gitar-bot[bot]

This comment was marked as resolved.

@qodo-code-review

Copy link
Copy Markdown
Contributor

Qodo Fixer

✅ Committed (1) · ☑ Fixed (1)

Grey Divider

Commits pushed directly to this PR — no separate fix PR opened.

Process — 1 fixed
  • ☑ Fixed: Account deletion scans every post key

@gitar-bot
gitar-bot Bot dismissed their stale review October 16, 2026 05:19

Auto-approval revoked — new findings require attention.

@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 8d5fc36

@qodo-code-review

Copy link
Copy Markdown
Contributor

Qodo Fixer

✅ Committed (1) · ☑ Fixed (1)

Grey Divider

Commits pushed directly to this PR — no separate fix PR opened.

Process — 1 fixed
  • ☑ Fixed: A switched account can duplicate a shift

gitar-bot[bot]

This comment was marked as resolved.

@gitar-bot
gitar-bot Bot dismissed their stale review October 16, 2026 07:10

Auto-approval revoked — new findings require attention.

@gitar-bot
gitar-bot Bot disabled auto-merge October 16, 2026 07:10
Signing out and back in as the same person on one tab kept the page's
in-memory post keys, so a deliberate repost could replay the earlier post.
The page now clears them on the account transition, with a test.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LuYbdEGXeMntVA9KMgtAEx
When the signed-in account changed mid-run, the stop path returned before
saving which dates had already gone live, so the same person retrying could
post a duplicate. The page now records the run's results before stopping,
records a request that was already answered, and names the dates that went
live in the stop message. Adds a test for the mid-post switch.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LuYbdEGXeMntVA9KMgtAEx

Copy link
Copy Markdown
Owner Author

Supabase Preview failed on ef8884a with "Failed to update config for branch". This failure isn't caused by this PR's change. That commit changes only the open-shifts page and its test, with no files under supabase/. The same preview passed on 96a88e3 and on 8d5fc36 a few minutes earlier. The most likely cause is the preview integration tripping over pushes that landed close together. No fix is needed in this PR, and I can't re-run a Supabase-hosted check from here. The PR required reds on 8d5fc36 and 96a88e3 came only from runs a newer push cancelled. Nothing in them failed. The current head's CI run is the one that counts.


Generated by Claude Code

@cubic-dev-ai cubic-dev-ai 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.

Review completed against the latest diff

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

@gitar-bot
gitar-bot Bot enabled auto-merge (squash) October 16, 2026 07:17
gitar-bot[bot]
gitar-bot Bot previously approved these changes Oct 16, 2026

@gitar-bot gitar-bot 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.

Gitar has auto-approved this PR (configure)

@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit ef8884a

The mid-post guard compared the roster's actor ID, which only updates after
the team list re-reads, so a sign-out could still let the next date send.
The page now also counts sign-outs on the tab and stops on any change.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LuYbdEGXeMntVA9KMgtAEx

@gitar-bot gitar-bot 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.

Gitar has auto-approved this PR (configure)

@gitar-bot

gitar-bot Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor
Code Review ✅ Approved 2 closed / 2 findings

🔴 High risk · The roster migration backfills poster IDs and adds an account-deletion foreign key; a constraint or backfill error could block roster writes or misattribute keys.

Open-shift post keys now persist in session storage across reloads, are cleared on sign-out and account change, and roster_open_post_keys.posted_by references auth.users with on delete set null. Resolved the migration seal mismatch and kept dates already posted in a stopped run so retries don't re-post them. No open issues found.

✅ 2 closed
✅ Bug: Migration edited after sealing: stored seal hash no longer matches

📄 supabase/migrations/20261015181500_roster_open_post_keys_posted_by_fk.sql:15 📄 supabase/schema.sql:34206
Commit 2444c3b adds create index roster_open_post_keys_posted_by_idx to 20261015181500_roster_open_post_keys_posted_by_fk.sql. That file was already sealed in e270353. supabase/applied-migration-hashes.json:317 still holds 57963eee…, but the file now hashes to 795084db… (sha256 of the LF-normalised content, the same way scripts/check-migration-immutability.mjs:49,61 computes it). So check:migration-immutability will fail on this PR. The new index is also missing from supabase/drift-manifest.json, which only lists roster_open_post_keys_open_shift_idx and _pkey, so the drift freshness and live-drift checks will report a mismatch too.

Fix: update the seal entry for this file and regenerate the drift manifest. The repo rules count editing an existing seal entry as a database change, so it needs the owner's explicit yes for this specific edit. The .gitleaksignore fingerprint is pinned to commit e270353 at line 317. If the reseal happens in a new commit, add a fingerprint for that commit. Another option is to put the index in its own new timestamped migration so the existing seal stays valid, though that migration still needs sealing and a manifest update.

✅ Bug: Stopped post drops dates already posted in this run, so a retry re-posts them

📄 src/components/open-shifts/open-shifts-post-page.tsx:291-298 📄 src/components/open-shifts/open-shifts-post-page.tsx:313-316
Take a multi-date post where date 1 succeeds. Its key is removed by forgetPostKey (L298) and its id goes into the local nowDone. If the account then changes while date 2 is in flight, the loop breaks and L314 returns before setDone(nowDone) / setUnanswered(nowUnanswered) run, so the page never records that date 1 went live. The component stays mounted through sign-out and sign-in, and the form keeps its dates. When the same person signs back in (for example after the session expires mid-post) and presses Yes, toPost includes date 1 again. It has no unanswered entry, so lostAnswerMatch skips it, and it has no stored key, so postKeyFor makes a fresh one. That posts a duplicate open shift, which is exactly what this PR is meant to stop. The stop message also hides the dates that did go live. Fix: save nowDone/nowUnanswered before returning on stop and name the posted dates in the message. Or hold the forgetPostKey calls until the loop finishes without stopping.

Verification

Rules No rules evaluated

Functional validation Not enabled

Set up

Auto-approval Approved

For more verification details, comment gitar display:verbose.

Options

Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Showing compact verification results.

Comment with these commands to change the behavior for this request:

Auto-apply Compact
gitar auto-apply:on         
gitar display:verbose         

Important

Your trial ends in 5 days — upgrade now to keep code review, CI analysis, auto-apply, custom automations, and more.

Was this helpful? React with 👍 / 👎 | Gitar

@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 9755975

Copy link
Copy Markdown
Owner Author

Production UI (3) failed on 9755975, and PR required failed only because it depends on that check. The only failing test is tests/ui-tools.spec.ts › "services query param fallback ignores whitespace-only q values". It failed in gotoLauncher with "Execution context was destroyed" while the hydration poll was running, and the other 261 tests passed.

This PR only changes the open-shifts post page, its tests and the roster migration. It doesn't touch the services launcher or the shared Playwright settlement helper, so the failure isn't caused by this change. No fix for it exists yet. I'm re-running the failed jobs once. If it fails again, I'll treat it as a real failure.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🟠 Medium blast radius size:L This PR changes 100-499 lines, ignoring generated files 🕐 40+ Minutes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants