test(provider): skip the keychain round-trip when the login keychain is locked - #58
Conversation
…when the login keychain is locked - `testKeychainStoreRoundTripsWithoutEntitlements` performs a real round-trip through the file-based login keychain on purpose — that is the contract it guards. In a non-interactive session (SSH, `make release` over a remote shell) the login keychain is locked and Security cannot raise the unlock panel, so `SecItemAdd` returns errSecInteractionNotAllowed (-25308) and `make gates` fails on an environment quirk, not on a product defect. - Catch that single status and `throw XCTSkip(...)` with an actionable message. Every other failure still fails the suite — in particular errSecMissingEntitlement (-34018), the regression this test exists to catch. - Hoist the keychain calls out of the XCTAssert autoclosures: XCTest swallows errors thrown inside an assertion and records them as failures, so a status raised there would never reach the catch. - Test-only change; no production code is touched. - Gate, over SSH on sztudio: `swift test --filter ProviderSettingsTests` → 17 tests, 1 skipped, 0 failures, exit 0; the skipped one is this test, reported as "login keychain locked in a non-interactive session". - Mutation check, same host: swapping the caught status to errSecMissingEntitlement turns the run into 17 tests, 1 failure, exit 1 — `caught error: "keychain(-25308)"` at ProviderSettings.swift:146 — proving the skip is targeted at -25308 and not a blanket catch. Authored-By: claude <agents@vetcoders.io> session_id: 01a0057d-7dd8-7421-8363-844e9cf10946 time: 2026-08-16T03:17:26+02:00 runtime: interactive
There was a problem hiding this comment.
An organization admin can view or raise the cap at claude.ai/admin-settings/claude-code. The cap resets at the start of the next billing period.
Once the cap resets or is raised, reopen this pull request to trigger a review.
There was a problem hiding this comment.
Pull request overview
Adjusts the ProviderSettings keychain round-trip unit test to handle a known non-interactive-session macOS keychain behavior so make release (run over SSH) can pass make gates without weakening the test’s intended entitlement regression coverage.
Changes:
- Adds a targeted
XCTSkipwhen Keychain returnserrSecInteractionNotAllowed(-25308), with an actionable skip message. - Refactors the test to run Keychain calls outside
XCTAssert*autoclosures so the skip path can be reliably triggered viado/catch. - Imports
Securityin the test file to reference the Keychain status constant.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Why
make releasefails inmake gatesonProviderSettingsTests.testKeychainStoreRoundTripsWithoutEntitlementswhenever the release is driven from a non-interactive session (SSH into the build host). This blocked the 0.4.4 release cut.The failure is an environment quirk, not a product defect — the same SHA is green on CI:
-25308iserrSecInteractionNotAllowed. The test deliberately performs a real round-trip through the file-based login keychain — that is precisely the contract it guards (aSecAccessControlvariant would route the item to the data-protection keychain, which Developer ID builds cannot touch, failing witherrSecMissingEntitlement/-34018). In an SSH session the login keychain is locked and Security cannot raise the unlock panel, soSecItemAddcannot succeed by any means available to the test.What changed
Test-only. No production code is touched.
throw XCTSkip(...)for exactly one status:errSecInteractionNotAllowed(-25308), with an actionable message — "login keychain locked in a non-interactive session — run from a GUI session or CI". This follows the existingXCTSkipidiom in the suite (PreviewThemeTests,WindowChromeRecipeTests,EditorToolbarOverflowTests).-34018— the regression this test exists to catch. The skip is a narrow environment gate, not a weakened assertion.XCTAssertautoclosures. XCTest swallows errors thrown inside an assertion and records them as failures, so a status raised there would never reach thecatch— without this, the skip would not fire reliably.Proof
Both runs on the build host over SSH — i.e. in the exact session shape that was failing.
Gate — the skip fires and the suite is green:
Mutation check — the skip is targeted, not a blanket catch. Temporarily swapping the caught status to
errSecMissingEntitlementand re-running on the same host:The -25308 path fails loudly once it is no longer the caught status, which is the evidence that the skip is bound to that one status and nothing else. The mutation was reverted; it is not part of this branch.
Full suite runs on CI.