Skip to content

Read tokens as empty when the secret store's type initializer failed - #302

Merged
matt-edmondson merged 2 commits into
mainfrom
fix/296-unwrap-type-initializer-failures
Sep 26, 2026
Merged

matt-edmondson merged 2 commits into
mainfrom
fix/296-unwrap-type-initializer-failures

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Fixes #296

What was wrong

On Linux, the credential store first reaches libsecret from a static constructor. So when libsecret-1.so.0 is missing, TokenStorage receives a TypeInitializationException that wraps the DllNotFoundException. IsUnavailable only matched the bare exception types, so the wrapped form escaped every Read and Write, and the runtime rethrows it on every later access. On a headless Linux host every Token getter threw, when the documented behaviour is "reads as empty, logged once".

Change

  • TokenStorage.IsUnavailable now unwraps a TypeInitializationException and classifies its inner exception.
  • New test double TypeInitializerFailureCredentialStore throws the wrapped form. A new test asserts that provider and owner tokens read as empty and that Write returns false.

Verification (Linux container without libsecret, .NET 10)

I ran every test class except GitHubRequestOutcomeTests, which is broken on main independently (#300, fixed in #301):

  • With the fix: 61 passed, 0 failed. That includes AzureDevOpsSessionTests.AProviderWithNoCredentialsHasNoSession and RepeatedLookupsOnAnUnconfiguredProviderStayNull, which hit the real, missing libsecret in this container.
  • With the fix reverted: 3 failed: the new test and those two AzureDevOpsSessionTests.

The "logged once" part isn't asserted, because the suite has no log-capture seam. ReportUnavailable is unchanged and still gates on its Interlocked flag.

🤖 Generated with Claude Code

https://claude.ai/code/session_01T3Dc4pH8DW9RqEzV5znBXQ


Generated by Claude Code

…iled [patch]

On Linux the credential store first reaches libsecret from a static
constructor, so a missing libsecret-1.so.0 arrives as a
TypeInitializationException wrapping the DllNotFoundException. IsUnavailable
only matched the bare exception types, so the wrapped form escaped every
TokenStorage read and write, and the runtime rethrows it on each later access.

Unwrap TypeInitializationException in IsUnavailable, and cover the wrapped
form with a test double.

Fixes #296

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3Dc4pH8DW9RqEzV5znBXQ
… in-memory store

GitHubRequestOutcomeTests built owners with `new Owner { ... }`, so BuildProvider
was null. Since tokens moved into the secret store (#285), reading or writing an
owner's token derives its persona from BuildProvider.Name, so the four workflow
action tests threw NullReferenceException on every platform.

Build owners with provider.CreateOwner(...), and inject an in-memory credential
store for every test so an authorization failure clearing the provider token
never reaches the developer's real OS secret store.

Fixes #300

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3Dc4pH8DW9RqEzV5znBXQ
(cherry picked from commit ecf9023)

Copy link
Copy Markdown
Contributor Author

Test on ubuntu-latest failed on 2137658 with 4 failures: the GitHubRequestOutcomeTests workflow-action tests threw NullReferenceException. They are not this PR's failures. They come from owners built without a provider (#300), which is also red on main.

I've ported the fix from #301 into this branch as 401a218 (cherry-picked). It becomes a no-op once either PR merges. With it, the full suite passes 72/72 locally on Linux.


Generated by Claude Code

@sonarqubecloud

Copy link
Copy Markdown

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.

On Linux without libsecret, every token read throws TypeInitializationException instead of reading as empty

2 participants