Skip to content

fix(auth)!: fix macOS Keychain consent prompt and storage contract - #1208

Merged
grdsdev merged 10 commits into
mainfrom
worktree/quiet-river-c505
Aug 14, 2026
Merged

fix(auth)!: fix macOS Keychain consent prompt and storage contract#1208
grdsdev merged 10 commits into
mainfrom
worktree/quiet-river-c505

Conversation

@grdsdev

@grdsdev grdsdev commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Closes SDK-164.

The reported problem

macOS users of apps embedding this SDK see a Keychain consent prompt naming supabase.gotrue.swift — a string with no visible connection to the host app. Users find it suspicious and some deny access (discussions#39584).

Why the originally-filed fix would not have worked

SDK-164 proposed omitting the service value to "use the default application keychain". Research before implementing showed that does not fix the problem, and is unsafe:

  • macOS SecItem targets the legacy file-based Keychain by default. Per TN3137: "The SecItem API can target either implementation. It defaults to targeting the file-based keychain." That is the Keychain with ACLs — and ACLs are what produce consent dialogs.
  • The prompt is an ACL / designated-requirement artifact, not an attribute one. Per Apple DTS: "If the DR changes, that's considered to be different code… you get a bunch of authorisation alerts." A Developer ID build reading an item written by a Development-signed build is, to the Keychain, different code. kSecAttrService plays no part in that decision.
  • Decisive counter-example: KeychainSwift never sets kSecAttrService at all, and its users hit the same dialog (keychain-swift#117).
  • Omitting it is actively unsafe. An omitted kSecAttrService is stored as the empty string, not as absent. Since the generic-password primary key is account + service + access group, we would share a namespace with any other library that also omits it — and deleteItem/SecItemUpdate both build from baseQuery, so those queries could match and destroy items this SDK never wrote.

What this PR does instead

1. Opt-in useDataProtectionKeychain — the change that actually removes the dialog. The data-protection Keychain has no ACL model, so it shows no consent prompt. It is off by default deliberately: setting it unconditionally is exactly what caused the -34018 errSecMissingEntitlement regression (#455#516 → reverted in #574). Auth0's SimpleKeychain independently reaches the same conclusion — it never sets the attribute and exposes it only as a consumer opt-in.

2. Default Keychain service is now the host app's bundle identifier. Previously every app embedding this SDK shared one hardcoded service. This is what "use the default app keychain" idiomatically means, and what both KeychainAccess (the dependency dropped in #403) and SimpleKeychain do. Falls back to the legacy constant when there is no bundle identifier, avoiding the force-unwrap bug SimpleKeychain still carries (#336). Existing sessions migrate automatically on first read — users are not signed out.

3. kSecAttrAccessible is now guarded on macOS. It does not apply to the file-based Keychain, so today it is inert; it is now sent only when data protection is enabled. Matches SimpleKeychain, which guards this and pins it with tests.

4. AuthLocalStorage.retrieve now returns nil for a missing key instead of throwing — a contract violation found along the way. The protocol always documented "returns nil if absent", but both KeychainLocalStorage and WinCredLocalStorage threw .itemNotFound. Consequences that are now fixed:

  • Every fresh launch with no session logged error "Failed to retrieve session" — a normal empty state reported as a failure.
  • CodeVerifierStorage's "Code verifier not found" debug branch was unreachable.
  • try? at the call sites collapsed a locked Keychain, an ACL denial, or an entitlement failure into the same nil as "no session" — turning a real error into a silent sign-out.

remove(key:) is also idempotent now.

Migration safety

retrieve reads the current location first and, only on a miss, probes legacy locations, moving whatever it finds. Migration is write-then-delete, so there is no data-loss window. The explicit init(service:) never migrates, so a caller who deliberately chose a service does not inherit data from the old hardcoded one. Two failure modes were closed during review: a failed primary delete no longer skips legacy cleanup (which could resurrect a signed-out session), and a failed primary write no longer discards a successfully-read legacy session.

Testing

1128 tests pass. Query construction and migration logic are covered by unit tests and an injected fake keychain.

Known limitation, stated plainly: there are no round-trip tests against the real Keychain, and there cannot be. An SPM test bundle has no provisioning profile, so kSecUseDataProtectionKeychain = true fails with the very -34018 this design works around. That is why the internal KeychainProtocol and fake exist. Verifying that the consent prompt is actually gone requires a signed macOS sample app and remains manual — this should be confirmed before anyone is advised to enable the flag.

Breaking changes

Three entries added to V3_MIGRATION.md covering the service default, the retrieve/remove contract, and the new opt-in flag with its entitlement hazard.

Follow-ups (not in this PR)

  • WinCredLocalStorage looks substantially broken: retrieve builds its target as "\(service)\\\(key))" with a stray ) that store lacks, so reads cannot match writes; it escapes a pointer out of withCString's closure; and store calls withUnsafeMutableBytes(of:) on a Data value, capturing the struct's bytes rather than its contents.
  • origin/grdsdev/auth-biometrics-keychain adds a second SecItem implementation that duplicates this logic; it should rebase onto this and adopt KeychainProtocol.
  • The three StorageMigration passes re-read storage on every session get.
  • scripts/test-docs.sh masks hard xcodebuild failures via || true.

A missing item threw .itemNotFound instead of returning nil, contradicting
AuthLocalStorage's documented contract. Every fresh launch with no session
logged an error, and try? at the call sites collapsed genuine Keychain
failures into the same nil as 'no session'. Delete is now idempotent.
Adds a useDataProtectionKeychain flag threaded through every query, and
stops sending kSecAttrAccessible on macOS unless it is enabled — the
file-based Keychain ignores that attribute, so today it is inert.
Items were namespaced under a fixed 'supabase.gotrue.swift' service shared by
every app embedding the SDK. The service now defaults to the host app's bundle
identifier, matching KeychainAccess and SimpleKeychain. Existing sessions
migrate on first read.
Review found the Keychain migration entry overstated what the prior
research established: it asserted the file-based Keychain prompt still
displays the literal old "supabase.gotrue.swift" string, but the
service-name-drives-dialog-text claim was only ever community-sourced,
not Apple-documented (per the design doc's Non-goals section, which is
also why a kSecAttrLabel change was dropped from this work). Reword to
describe only the documented mechanism instead: the prompt is tied to
the app's designated requirement/ACL, governed by code-signing
identity rather than kSecAttrService, per Apple TN3137.
remove(key:) previously exited before running the legacy-cleanup loop
whenever the primary delete threw, leaving a stale legacy session in
place. The next retrieve would migrate it back, signing a signed-out
user back in. remove now always attempts every legacy location and
re-throws the primary error afterward.

retrieve(key:) previously discarded a successfully-read legacy value
if the migration write to the primary location failed, surfacing the
error and making the user appear signed out despite having a valid
stored session. The write and the legacy delete are now sequenced so
a failed write leaves the legacy copy in place (for the next read to
retry) while still returning the value that was read.

Also documents the best-effort semantics in the DocC comments, scopes
an overclaim in V3_MIGRATION.md about cross-app Keychain collisions
to the platforms where it actually applies, and extends FakeKeychain
with writeError/deleteError to cover both fixes with new tests.
@grdsdev
grdsdev requested a review from a team as a code owner August 13, 2026 16:48
@coderabbitai

coderabbitai Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 72e47939-aa31-4ca4-93f5-7225b52ea74d

📥 Commits

Reviewing files that changed from the base of the PR and between 4caea51 and 5142f40.

📒 Files selected for processing (3)
  • Examples/KeychainMigrationDemo/README.md
  • Examples/KeychainMigrationDemo/Sources/KeychainMigrationDemo/main.swift
  • Examples/KeychainMigrationDemo/run.sh
🚧 Files skipped from review as they are similar to previous changes (1)
  • Examples/KeychainMigrationDemo/run.sh

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added optional data-protection Keychain support.
    • Added automatic migration of existing sessions from legacy Keychain locations.
    • Keychain service now defaults to the app’s bundle identifier.
    • Added configurable storage initialization for custom services and access groups.
  • Bug Fixes

    • Missing values now return nil, and removing missing values succeeds without errors.
    • Genuine Keychain failures continue to be reported.
  • Documentation

    • Documented migration behavior, storage changes, and macOS entitlement requirements.
    • Added a Keychain migration demonstration and usage guide.

Walkthrough

Keychain now supports data-protection configuration, optional reads for missing items, and successful deletion of absent items. KeychainLocalStorage uses a bundle-based primary location, probes legacy locations, and migrates found values. Explicit services disable migration. The macOS demo inspects Keychain state, logs results, and runs migration commands. Tests and migration documentation cover the updated behavior.

Sequence Diagram(s)

sequenceDiagram
  participant Demo
  participant SupabaseClient
  participant KeychainLocalStorage
  participant Keychain
  Demo->>SupabaseClient: Sign in or read session
  SupabaseClient->>KeychainLocalStorage: Retrieve session
  KeychainLocalStorage->>Keychain: Read primary location
  Keychain-->>KeychainLocalStorage: Value or nil
  KeychainLocalStorage->>Keychain: Probe legacy location
  Keychain-->>KeychainLocalStorage: Legacy value
  KeychainLocalStorage->>Keychain: Store migrated value
  KeychainLocalStorage-->>SupabaseClient: Return session
  SupabaseClient-->>Demo: Report session and Keychain state
Loading
✨ Finishing Touches 💡 1
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch worktree/quiet-river-c505

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.

@coveralls

coveralls commented Aug 13, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 31825654703

Coverage increased (+0.5%) to 86.84%

Details

  • Coverage increased (+0.5%) from the base build.
  • Patch coverage: 1 uncovered change across 1 file (126 of 127 lines covered, 99.21%).
  • 4 coverage regressions across 1 file.

Uncovered Changes

File Changed Covered %
Sources/Auth/Internal/Keychain.swift 37 36 97.3%
Total (2 files) 127 126 99.21%

Coverage Regressions

4 previously-covered lines in 1 file lost coverage.

File Lines Losing Coverage Coverage
Sources/Auth/Internal/Keychain.swift 4 80.24%

Coverage Stats

Coverage Status
Relevant Lines: 10395
Covered Lines: 9027
Line Coverage: 86.84%
Coverage Strength: 43.24 hits per line

💛 - Coveralls

@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Capability matrix drift detected

The following capabilities are marked implemented in swift but have no registered symbols to verify:

  • auth.passkey.register_passkey (no symbols list — cannot confirm implementation exists)
  • auth.passkey.sign_in_with_passkey (no symbols list — cannot confirm implementation exists)
  • client.authentication_integration.third_party_auth (no symbols list — cannot confirm implementation exists)
  • client.authentication_integration.cross_client_token_sync (no symbols list — cannot confirm implementation exists)
  • client.authentication_integration.oauth_flow_type (no symbols list — cannot confirm implementation exists)
  • client.authentication_integration.session_url_detection (no symbols list — cannot confirm implementation exists)
  • client.session_management.custom_storage (no symbols list — cannot confirm implementation exists)
  • client.session_management.persist_session (no symbols list — cannot confirm implementation exists)
  • client.request_configuration.global_headers (no symbols list — cannot confirm implementation exists)
  • client.observability.trace_propagation (no symbols list — cannot confirm implementation exists)
  • database.query.select (no symbols list — cannot confirm implementation exists)
  • database.query.schema_selection (no symbols list — cannot confirm implementation exists)
  • database.query.rpc (no symbols list — cannot confirm implementation exists)
  • database.mutate.insert (no symbols list — cannot confirm implementation exists)
  • database.mutate.update (no symbols list — cannot confirm implementation exists)
  • database.mutate.upsert (no symbols list — cannot confirm implementation exists)
  • database.mutate.delete (no symbols list — cannot confirm implementation exists)
  • database.mutate.select_after_mutation (no symbols list — cannot confirm implementation exists)
  • database.using_filters.eq (no symbols list — cannot confirm implementation exists)
  • database.using_filters.neq (no symbols list — cannot confirm implementation exists)
  • database.using_filters.gt (no symbols list — cannot confirm implementation exists)
  • database.using_filters.gte (no symbols list — cannot confirm implementation exists)
  • database.using_filters.lt (no symbols list — cannot confirm implementation exists)
  • database.using_filters.lte (no symbols list — cannot confirm implementation exists)
  • database.using_filters.like (no symbols list — cannot confirm implementation exists)
  • database.using_filters.ilike (no symbols list — cannot confirm implementation exists)
  • database.using_filters.is (no symbols list — cannot confirm implementation exists)
  • database.using_filters.in (no symbols list — cannot confirm implementation exists)
  • database.using_filters.contains (no symbols list — cannot confirm implementation exists)
  • database.using_filters.contained_by (no symbols list — cannot confirm implementation exists)
  • database.using_filters.range_gt (no symbols list — cannot confirm implementation exists)
  • database.using_filters.range_gte (no symbols list — cannot confirm implementation exists)
  • database.using_filters.range_lt (no symbols list — cannot confirm implementation exists)
  • database.using_filters.range_lte (no symbols list — cannot confirm implementation exists)
  • database.using_filters.range_adjacent (no symbols list — cannot confirm implementation exists)
  • database.using_filters.overlaps (no symbols list — cannot confirm implementation exists)
  • database.using_filters.text_search (no symbols list — cannot confirm implementation exists)
  • database.using_filters.match (no symbols list — cannot confirm implementation exists)
  • database.using_filters.not (no symbols list — cannot confirm implementation exists)
  • database.using_filters.or (no symbols list — cannot confirm implementation exists)
  • database.using_filters.raw (no symbols list — cannot confirm implementation exists)
  • database.using_filters.regex (no symbols list — cannot confirm implementation exists)
  • database.using_filters.regex_icase (no symbols list — cannot confirm implementation exists)
  • database.using_filters.is_distinct (no symbols list — cannot confirm implementation exists)
  • database.using_filters.like_all (no symbols list — cannot confirm implementation exists)
  • database.using_filters.like_any (no symbols list — cannot confirm implementation exists)
  • database.using_filters.ilike_all (no symbols list — cannot confirm implementation exists)
  • database.using_filters.ilike_any (no symbols list — cannot confirm implementation exists)
  • database.using_modifiers.order (no symbols list — cannot confirm implementation exists)
  • database.using_modifiers.limit (no symbols list — cannot confirm implementation exists)
  • database.using_modifiers.range (no symbols list — cannot confirm implementation exists)
  • database.using_modifiers.single_row (no symbols list — cannot confirm implementation exists)
  • database.using_modifiers.strip_nulls (no symbols list — cannot confirm implementation exists)
  • database.using_modifiers.format_csv (no symbols list — cannot confirm implementation exists)
  • database.using_modifiers.format_geojson (no symbols list — cannot confirm implementation exists)
  • database.using_modifiers.max_affected_rows (no symbols list — cannot confirm implementation exists)
  • database.using_modifiers.request_cancellation (no symbols list — cannot confirm implementation exists)
  • database.configuration.auto_retry (no symbols list — cannot confirm implementation exists)
  • functions.invocation.invoke (no symbols list — cannot confirm implementation exists)
  • functions.invocation.set_auth_token (no symbols list — cannot confirm implementation exists)
  • functions.invocation.method_override (no symbols list — cannot confirm implementation exists)
  • functions.invocation.streaming_response (no symbols list — cannot confirm implementation exists)
  • functions.invocation.request_cancellation (no symbols list — cannot confirm implementation exists)
  • realtime.client.connect (no symbols list — cannot confirm implementation exists)
  • realtime.client.disconnect (no symbols list — cannot confirm implementation exists)
  • realtime.client.get_channels (no symbols list — cannot confirm implementation exists)
  • realtime.client.remove_channel (no symbols list — cannot confirm implementation exists)
  • realtime.client.remove_all_channels (no symbols list — cannot confirm implementation exists)
  • realtime.client.connection_state (no symbols list — cannot confirm implementation exists)
  • realtime.client.listen_heartbeats (no symbols list — cannot confirm implementation exists)
  • realtime.client.set_auth_token (no symbols list — cannot confirm implementation exists)
  • realtime.client.channel (no symbols list — cannot confirm implementation exists)
  • realtime.channel.subscribe (no symbols list — cannot confirm implementation exists)
  • realtime.channel.unsubscribe (no symbols list — cannot confirm implementation exists)
  • realtime.channel.broadcast (no symbols list — cannot confirm implementation exists)
  • realtime.channel.broadcast_http (no symbols list — cannot confirm implementation exists)
  • realtime.subscriptions.postgres_changes (no symbols list — cannot confirm implementation exists)
  • realtime.subscriptions.subscribe_presence (no symbols list — cannot confirm implementation exists)
  • realtime.subscriptions.private_channel (no symbols list — cannot confirm implementation exists)
  • realtime.subscriptions.broadcast_self (no symbols list — cannot confirm implementation exists)
  • realtime.subscriptions.broadcast_ack (no symbols list — cannot confirm implementation exists)
  • realtime.subscriptions.broadcast_replay (no symbols list — cannot confirm implementation exists)
  • realtime.presence.track (no symbols list — cannot confirm implementation exists)
  • realtime.presence.untrack (no symbols list — cannot confirm implementation exists)
  • realtime.presence.presence_key (no symbols list — cannot confirm implementation exists)
  • realtime.configuration.custom_websocket_transport (no symbols list — cannot confirm implementation exists)
  • realtime.configuration.reconnect_backoff (no symbols list — cannot confirm implementation exists)
  • realtime.configuration.heartbeat_interval (no symbols list — cannot confirm implementation exists)
  • realtime.configuration.access_token_callback (no symbols list — cannot confirm implementation exists)
  • realtime.configuration.deferred_disconnect (no symbols list — cannot confirm implementation exists)
  • realtime.configuration.custom_logger (no symbols list — cannot confirm implementation exists)
  • realtime.configuration.binary_protocol (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.get_bucket (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.list_file_buckets (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.update_bucket (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.delete_file_bucket (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.empty_bucket (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.access_bucket (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.upload (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.download (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.move (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.copy (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.remove (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.create_signed_url (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.create_signed_urls (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.create_signed_upload_url (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.upload_with_signed_url (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.update_file (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.file_exists (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.file_info (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.copy_cross_bucket (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.move_cross_bucket (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.upload_with_metadata (no symbols list — cannot confirm implementation exists)
  • storage.file_buckets.url_cache_nonce (no symbols list — cannot confirm implementation exists)

These may have been renamed, removed, or never registered. Please update the capability matrix.
See: https://github.com/supabase/sdk/blob/main/docs/capability-matrix.md

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

Updates Apple-platform Auth storage to improve macOS Keychain behavior and align missing-item semantics.

Changes:

  • Adds opt-in data-protection Keychain support.
  • Uses bundle-scoped services with legacy migration.
  • Makes missing reads and deletes non-errors, with tests and migration guidance.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
V3_MIGRATION.md Documents storage and Keychain changes.
Sources/Auth/Internal/Keychain.swift Adds query options and status mapping.
Sources/Auth/Storage/KeychainLocalStorage.swift Implements configuration and legacy migration.
Tests/AuthTests/KeychainTests.swift Tests Keychain queries and status handling.
Tests/AuthTests/KeychainLocalStorageTests.swift Tests migration and failure behavior.
dictionary.txt Adds migration-document terminology.
Suppressed comments (2)

Sources/Auth/Storage/KeychainLocalStorage.swift:127

  • Swallowing a legacy deletion error lets remove report success while an old credential remains. The next retrieve can migrate that credential back into the primary location, resurrecting a signed-out session. Attempt every deletion, but retain and throw the first primary or legacy error after the loop, with a regression test for legacy-delete failure.
      for legacy in legacyKeychains {
        try? legacy.deleteItem(forKey: key)

Sources/Auth/Storage/KeychainLocalStorage.swift:163

  • When data protection is enabled, this orders the pre-v3 hardcoded service before the immediately previous bundle-scoped file-based location appended below. Because migration intentionally tolerates failure to delete the old copy, a stale pre-v3 session can coexist with a newer bundle-scoped session; enabling the flag then migrates and returns the stale value without examining the newer one. Probe the same-service file-based location first and update the ordering test.
      var candidates: [KeychainConfiguration] = [
        KeychainConfiguration(
          service: legacyKeychainService,
          accessGroup: primary.accessGroup,
          useDataProtectionKeychain: false

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread Sources/Auth/Storage/KeychainLocalStorage.swift Outdated
Comment thread V3_MIGRATION.md Outdated
Comment thread V3_MIGRATION.md
Comment thread Sources/Auth/Storage/KeychainLocalStorage.swift
Address PR review feedback.

The `try?` on the legacy probe was justified by "a failing probe must not
break a fresh install", but that rationale went stale once `data(forKey:)`
started mapping errSecItemNotFound to nil. An absent legacy item now reads
as nil, so anything thrown from the probe is a genuine failure — a locked
Keychain, a denied ACL prompt — and swallowing it reports "no session" for
what is really an error. That is the exact failure this PR set out to fix,
reintroduced one layer down.

The `try?` on the two legacy *delete* sites is kept: best-effort cleanup of
a legacy copy should not fail a sign-out or an otherwise successful
migration, and both are documented as such.

Also corrects two migration-guide overclaims:
- The missing-key contract entry was written as if it fixed AuthLocalStorage
  protocol-wide. It fixes the Apple implementation only; WinCredLocalStorage
  still throws on ERROR_NOT_FOUND. Scoped the heading and body, and noted
  the Windows implementation is being dropped in v3 separately.
- Enabling the data-protection Keychain does not avoid the consent prompt on
  the upgrade read: the migration deliberately probes the old file-based
  location, which can show the prompt one last time before the value moves.
Resolves a conflict in V3_MIGRATION.md.

The conflict was purely positional: both sides appended new `##` sections
at the end of the same region. main added the swift-log migration entry
(#1210); this branch added three Keychain entries. Neither touches the
other's subject matter, so both are kept — main's Logging section first,
then the Keychain ones, so this branch still reads as "appended at the
end".

Checked for real interactions and found none: the Keychain entries make no
reference to SupabaseLogger or the logging API that #1210 removed, and this
branch changes no Codable conformance, so it does not intersect #1209's
narrowing pass. dictionary.txt merged cleanly, keeping both sides' terms.
@grdsdev
grdsdev force-pushed the worktree/quiet-river-c505 branch from 5142f40 to 47be30f Compare August 14, 2026 17:43
@grdsdev
grdsdev enabled auto-merge (squash) August 14, 2026 17:47
@grdsdev
grdsdev merged commit 528bd5f into main Aug 14, 2026
31 of 32 checks passed
@grdsdev
grdsdev deleted the worktree/quiet-river-c505 branch August 14, 2026 17:54
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.

4 participants