Skip to content

[AI-3363] Log the server's error code and bearer expiry on every 401 - #1232

Merged
realtonyyoung merged 7 commits into
mainfrom
tonyyoung/ai-3363-log-401-reason
Sep 30, 2026
Merged

realtonyyoung merged 7 commits into
mainfrom
tonyyoung/ai-3363-log-401-reason

Conversation

@realtonyyoung

Copy link
Copy Markdown
Collaborator

AI-3363 (no GitHub issue)

What & why

The daemon and watcher log a 401 as a bare status, so a refused bearer cannot be told apart from a lapsed one without the server's log. A new BearerRejectionReportHandler reports every 401 with the server's error code and the refused bearer's subject, issue time and expiry against local time — never the bearer or the query string — and leaves the response untouched. It is installed on the daemon's hub, the daemon's agent-run event queue, and the watcher's hub.

Where to look

The hubs install it through HttpMessageHandlerFactory, which places it inside SignalR's own token handler; that is what lets it see the bearer SignalR sent. The body is buffered (capped at 4 KiB) so a caller can still read it.

Verification

A SignalR client against a stub server answering negotiate with a 401 reports invalid_token and the subject of the bearer the token provider supplied, proving the placement. Unit tests pin the report for an expired bearer, no bearer, and an unreadable body or token; that other statuses report nothing; and that the caller still reads the body. JwtClaims.TryGetTime is pinned for numeric, non-numeric and out-of-range claims. Release AOT publish of the CLI and daemon shows no trim or AOT warnings.

🤖 Generated with Claude Code

The handler must sit inside SignalR's token handler to see the bearer it sent, so it is
installed through HttpMessageHandlerFactory rather than wrapped around the client.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@linear-code

linear-code Bot commented Sep 30, 2026

Copy link
Copy Markdown

AI-3363

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Report bearer rejection details for daemon and watcher 401s

✨ Enhancement 🧪 Tests 🕐 20-40 Minutes

Grey Divider

AI Description

• Report server error codes and bearer timing on daemon and watcher 401 responses.
• Keep tokens and query strings out of reports without consuming response bodies.
• Test JWT claim parsing, 401 reporting, and SignalR handler placement.
Diagram

graph TD
  D["Daemon hub"] --> R["401 reporter"] --> L["Warning log"]
  W["Watcher hub"] --> R --> C["JWT claims"]
  Q["Event queue"] --> R --> L
  S["401 response"] --> R --> L
Loading
High-Level Assessment

The shared delegating handler is appropriate: SignalR's handler factory places it where it can inspect the bearer actually sent, while bounded buffering preserves the response for callers. Logging only at connection failure would lose that request-level context.

Files changed (7) +251 / -8

Enhancement (4) +100 / -8
JwtClaims.csRead JWT NumericDate claims for diagnostics +17/-7

Read JWT NumericDate claims for diagnostics

• Adds nullable Unix-time claim extraction and shares payload decoding with string-claim reads. Invalid, nonnumeric, or out-of-range values return null.

src/Capacitor.Cli.Core/Auth/JwtClaims.cs

BearerRejectionReportHandler.csReport 401 error codes and bearer claim timing +70/-0

Report 401 error codes and bearer claim timing

• Adds a shared HTTP handler that reports the server error code, bearer subject, issue time, expiry, and local time without logging the token or query string. It buffers up to 4 KiB of response content so callers can still read it.

src/Capacitor.Cli.Core/Http/BearerRejectionReportHandler.cs

ServerConnection.csWire rejection reporting into both daemon HTTP lanes +11/-1

Wire rejection reporting into both daemon HTTP lanes

• Installs the handler on the daemon's SignalR connection and agent-run event queue. Reports flow to the daemon's warning logger.

src/Capacitor.Cli.Daemon/Services/ServerConnection.cs

WatchCommand.csReport watcher hub bearer rejections +2/-0

Report watcher hub bearer rejections

• Installs the handler through SignalR's HTTP handler factory and routes reports to the watcher's log output.

src/Capacitor.Cli/Commands/WatchCommand.cs

Tests (3) +151 / -0
JwtClaimsTests.csCover NumericDate claim parsing +12/-0

Cover NumericDate claim parsing

• Tests numeric time extraction and null results for missing, nonnumeric, and out-of-range claims.

test/Capacitor.Cli.Core.Tests.Unit/Auth/JwtClaimsTests.cs

BearerRejectionReportHandlerTests.csCover 401 reports and response preservation +87/-0

Cover 401 reports and response preservation

• Tests expired, absent, and unreadable bearers; unreadable bodies; and silence for non-401 statuses. Also checks that reports omit the token and query string while leaving the response body readable.

test/Capacitor.Cli.Core.Tests.Unit/Http/BearerRejectionReportHandlerTests.cs

HubBearerRejectionReportTests.csVerify SignalR reports the bearer it sent +52/-0

Verify SignalR reports the bearer it sent

• Uses a stubbed negotiate 401 to verify that a factory-installed handler reports the server error code and the subject from SignalR's supplied bearer.

test/Capacitor.Cli.Tests.Integration/HubBearerRejectionReportTests.cs

@qodo-code-review

qodo-code-review Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

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

Grey Divider


Action required

1. Callers can lose a rejected response body ✓ Resolved
Description
ReadErrorCodeAsync calls LoadIntoBufferAsync with a 4 KiB limit and returns the same response
after suppressing a buffering failure. When a 401 has no known content length and its body exceeds
that limit, buffering consumes the response stream before the caller can read it.
Code

src/Capacitor.Cli.Core/Http/BearerRejectionReportHandler.cs[R54-57]

+        if (response.Content.Headers.ContentLength is > MaxBodyBytes) return null;
+
+        try {
+            await response.Content.LoadIntoBufferAsync(MaxBodyBytes, ct);
Relevance

●● Moderate

Potential stream-consumption bug is substantive, but historical evidence lacked a closely matching
precedent.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The length check only skips known-large bodies. The subsequent limited buffering attempt can consume
an unknown-length stream before the exception is suppressed, while the handler still returns that
response; the body-preservation test covers only a small StringContent response.

src/Capacitor.Cli.Core/Http/BearerRejectionReportHandler.cs[16-27]
src/Capacitor.Cli.Core/Http/BearerRejectionReportHandler.cs[53-68]
test/Capacitor.Cli.Core.Tests.Unit/Http/BearerRejectionReportHandlerTests.cs[17-19]

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 lengthless 401 body larger than 4 KiB can be consumed while the diagnostic tries to buffer it, leaving the caller unable to read the original response.

## Fix Focus Areas
- src/Capacitor.Cli.Core/Http/BearerRejectionReportHandler.cs[53-68]
- test/Capacitor.Cli.Core.Tests.Unit/Http/BearerRejectionReportHandlerTests.cs[29-60]

## Recommended Fix
Read a bounded prefix without discarding the underlying response: replay any bytes consumed when returning the response, including when the limit is exceeded. Add a test with an unknown-length body larger than the limit that verifies the caller receives its full original body.

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



Remediation recommended

2. Quoted challenge text spoofs error codes ✗ Dismissed
Description
ChallengeError splits Bearer parameters on every comma, including commas inside quoted values, and
accepts an error= fragment from that split. For a challenge such as `Bearer
error_description="detail, error=fake", error="invalid_token", the daemon or watcher reports fake`
instead of the actual server error.
Code

src/Capacitor.Cli.Core/Http/BearerRejectionReportHandler.cs[R96-100]

+            foreach (var part in parameters.Split(',')) {
+                var pair = part.Split('=', 2, StringSplitOptions.TrimEntries);
+
+                if (pair.Length == 2 && pair[0].Equals("error", StringComparison.OrdinalIgnoreCase))
+                    return pair[1].Trim('"') is { Length: > 0 } error ? error : null;
Relevance

●●● Strong

Deterministic parser bug; quoted commas can spoof error codes, directly undermining this PR’s stated
logging purpose.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The parser splits parameters without tracking quotes, then returns the first fragment named error.
The existing test establishes that error_description is an accepted parameter, while both HTTP and
WebSocket reporting paths use this parser.

src/Capacitor.Cli.Core/Http/BearerRejectionReportHandler.cs[88-100]
src/Capacitor.Cli.Core/Http/BearerRejectionReportHandler.cs[29-33]
src/Capacitor.Cli.Core/Http/BearerRejectionReportHandler.cs[63-69]
test/Capacitor.Cli.Core.Tests.Unit/Http/BearerRejectionReportHandlerTests.cs[144-151]

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

## Issue description
`ChallengeError` treats commas inside quoted Bearer parameter values as separators, allowing text in `error_description` to be reported as the `error` code.

## Fix Focus Areas
- src/Capacitor.Cli.Core/Http/BearerRejectionReportHandler.cs[88-104]
- test/Capacitor.Cli.Core.Tests.Unit/Http/BearerRejectionReportHandlerTests.cs[144-151]

## Recommended Fix
Split Bearer parameters only at commas outside quoted strings, accounting for escaped quotes. Add a test with `error_description="detail, error=fake"` before `error="invalid_token"` and assert that `invalid_token` is reported.

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


3. Other schemes can supply the logged error ✗ Dismissed
Description
ChallengeError searches the entire challenge string for error=" without checking that the
parameter belongs to a Bearer challenge. If a 401 presents a non-Bearer challenge with an error
parameter, the daemon or watcher logs that value as the refused bearer’s server error.
Code

src/Capacitor.Cli.Core/Http/BearerRejectionReportHandler.cs[R89-93]

+        const string marker = "error=\"";
+
+        var start = challenge.IndexOf(marker, StringComparison.OrdinalIgnoreCase);
+
+        if (start < 0) return null;
Relevance

●●● Strong

Directly fixes incorrect error attribution by restricting parsing to Bearer challenges, matching the
PR’s stated intent.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new parser has no scheme check: its unrestricted substring search returns an error from a string
such as Basic error="invalid_credentials". Both 401 paths pass a challenge directly to this
parser.

src/Capacitor.Cli.Core/Http/BearerRejectionReportHandler.cs[86-101]
src/Capacitor.Cli.Core/Http/BearerRejectionReportHandler.cs[29-30]
src/Capacitor.Cli.Core/Http/BearerRejectionReportHandler.cs[64-68]

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 challenge parser attributes an error parameter from any authentication scheme to the refused bearer.
## Fix Focus Areas
- src/Capacitor.Cli.Core/Http/BearerRejectionReportHandler.cs[84-101]
- test/Capacitor.Cli.Core.Tests.Unit/Http/BearerRejectionReportHandlerTests.cs[143-150]
## Recommended Fix
Verify the Bearer scheme and parse its parameters before accepting an error value. Test that a non-Bearer challenge containing an error parameter does not supply the bearer error.

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


4. Later bearer challenges lose their error ✗ Dismissed
Description
SendAsync and ConnectWebSocketAsync pass only the first WWW-Authenticate challenge to
ChallengeError. If a 401 lists another challenge before Bearer error="invalid_token", both the
daemon and watcher report server error=- despite receiving the bearer error.
Code

src/Capacitor.Cli.Core/Http/BearerRejectionReportHandler.cs[R64-68]

+                    var challenge = webSocket.HttpResponseHeaders?.TryGetValue("WWW-Authenticate", out var values) == true
+                        ? values.FirstOrDefault()
+                        : null;
+
+                    report(Describe("GET", uri, ChallengeError(challenge), string.IsNullOrWhiteSpace(bearer) ? null : bearer, time.GetUtcNow()));
Relevance

●●● Strong

Directly fixes a correctness gap in the PR’s challenge-based error reporting; later Bearer
challenges must be considered.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Both new fallback paths select FirstOrDefault(), while the parser searches only the one string
supplied. The integration fixture supplies just one challenge, so it does not cover a later Bearer
challenge.

src/Capacitor.Cli.Core/Http/BearerRejectionReportHandler.cs[29-30]
src/Capacitor.Cli.Core/Http/BearerRejectionReportHandler.cs[64-68]
src/Capacitor.Cli.Core/Http/BearerRejectionReportHandler.cs[86-101]
test/Capacitor.Cli.Tests.Integration/HubBearerRejectionReportTests.cs[53-58]

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 401 fallback examines only the first authentication challenge, so a later Bearer challenge's error is omitted.
## Fix Focus Areas
- src/Capacitor.Cli.Core/Http/BearerRejectionReportHandler.cs[29-30]
- src/Capacitor.Cli.Core/Http/BearerRejectionReportHandler.cs[64-68]
- src/Capacitor.Cli.Core/Http/BearerRejectionReportHandler.cs[84-101]
## Recommended Fix
Examine all challenge values in both HTTP and WebSocket responses, select the Bearer challenge, and extract its error. Add coverage with a non-Bearer challenge first.

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


View medium (6)
5. WebSocket refusals omit server error ✗ Dismissed
Description
ConnectWebSocketAsync passes errorCode: null for every refused upgrade, while kcap-server
distinguishes token_expired from invalid_token in its JSON challenge body. When negotiate
succeeds but the /hubs/sessions upgrade returns 401, the daemon and watcher log server error=-
instead of the server’s reason.
Code

src/Capacitor.Cli.Core/Http/BearerRejectionReportHandler.cs[R61-63]

+            if (webSocket.HttpStatusCode == HttpStatusCode.Unauthorized) {
+                try {
+                    report(Describe("GET", uri, errorCode: null, string.IsNullOrWhiteSpace(bearer) ? null : bearer, time.GetUtcNow()));
Relevance

●●● Strong

PR intent explicitly requires server error codes; WebSocket handling is the clear remaining
omission.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Both server authentication strategies put the distinguishing error code in the 401 JSON body. The
newly added WebSocket factory ignores that body and explicitly reports a null code; the integration
test expects the resulting server error=-.

kcap-cli -> kcap-server
src/Capacitor.Cli.Core/Http/BearerRejectionReportHandler.cs[49-64]
test/Capacitor.Cli.Tests.Integration/HubBearerRejectionReportTests.cs[53-78]
External repo: kurrent-io/kcap-server, src/Capacitor.Server.Auth/WorkOSAuthStrategy.cs [135-145]
External repo: kurrent-io/kcap-server, src/Capacitor.Server.Auth/GitHubAppAuthStrategy.cs [127-137]

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 WebSocket factory reports every upgrade 401 without the server's error code because the code is available only in the server's JSON challenge body, not in the response details used by this factory.
## Fix Focus Areas
- src/Capacitor.Cli.Core/Http/BearerRejectionReportHandler.cs[60-64]
- /cross_repos/kcap-server/src/Capacitor.Server.Auth/WorkOSAuthStrategy.cs[135-145]
- /cross_repos/kcap-server/src/Capacitor.Server.Auth/GitHubAppAuthStrategy.cs[127-137]
## Recommended Fix
Coordinate with kcap-server to expose the challenge code in a response header on bearer-authentication 401s, then read that header from the WebSocket response details when building the CLI report. Keep the existing JSON response for HTTP callers.

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


6. A catch comment repeats the stubbed result ✓ Resolved
Description
The comment Refused at negotiate, as stubbed. only restates the response configured immediately
above the test. A reader of this catch block gets no explanation of a constraint or failure mode
that the stub and assertion do not already show.
Code

test/Capacitor.Cli.Tests.Integration/HubBearerRejectionReportTests.cs[46]

+            // Refused at negotiate, as stubbed.
Relevance

●●● Strong

Recent test-review precedents accept removing comments that merely restate setup or assertions.

PR-#1184
PR-#974

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The added comment describes the 401 negotiate refusal already set up by the test, without adding an
invariant or rationale.

Rule 2762993: Restrict comments to documenting non-obvious, behavior‑critical constraints
test/Capacitor.Cli.Tests.Integration/HubBearerRejectionReportTests.cs[27-31]
test/Capacitor.Cli.Tests.Integration/HubBearerRejectionReportTests.cs[43-50]

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 catch comment repeats the test's configured refusal without explaining a non-obvious constraint.

## Fix Focus Areas
- test/Capacitor.Cli.Tests.Integration/HubBearerRejectionReportTests.cs[43-47]

## Recommended Fix
Remove the comment, or replace it only if the catch needs to document a specific, behavior-critical reason.

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


7. Time-claim parsing bypasses the JSON helper ✓ Resolved
Description
JwtClaims.TryGetTime compares value.ValueKind directly with JsonValueKind.Number instead of
using the available IsNumber helper. Every numeric-date claim parsed through this new method takes
that direct-check path.
Code

src/Capacitor.Cli.Core/Auth/JwtClaims.cs[15]

+            root.TryGetProperty(claimName, out var value) && value.ValueKind == JsonValueKind.Number && value.TryGetInt64(out var seconds)
Relevance

●●● Strong

Recent repository precedent accepts replacing direct JsonValueKind checks with shared JSON helpers.

PR-#1016
PR-#546

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The changed time-claim check directly compares ValueKind with JsonValueKind.Number; the
referenced extension defines an equivalent IsNumber property and is imported by the core project.

Rule 2270023: Use JsonElementExtensions helpers instead of direct JsonValueKind comparisons
src/Capacitor.Cli.Core/Auth/JwtClaims.cs[13-17]
src/Capacitor.Models.Transcripts/JsonElementExtensions.cs[5-13]
src/Capacitor.Cli.Core/Capacitor.Cli.Core.csproj[40-44]

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 new time-claim parser uses a direct JSON kind comparison despite an available helper.

## Fix Focus Areas
- src/Capacitor.Cli.Core/Auth/JwtClaims.cs[15-15]

## Recommended Fix
Replace the `ValueKind` comparison with `value.IsNumber`, preserving the existing integer conversion and null behavior.

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


8. Error-code parsing bypasses JSON helpers ✓ Resolved
Description
ReadErrorCodeAsync compares the response root and error property's ValueKind directly instead
of using IsObject and IsString. Both checks run when the new handler parses a buffered 401
response body.
Code

src/Capacitor.Cli.Core/Http/BearerRejectionReportHandler.cs[R61-63]

+            return doc.RootElement.ValueKind == JsonValueKind.Object
+                && doc.RootElement.TryGetProperty("error", out var error)
+                && error.ValueKind == JsonValueKind.String
Relevance

●●● Strong

Recent precedent accepts standardizing direct JSON kind checks with shared helpers.

PR-#1016

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The changed response parser directly checks the object and string kinds; the referenced extension
defines both equivalent properties and is imported by the core project.

Rule 2270023: Use JsonElementExtensions helpers instead of direct JsonValueKind comparisons
src/Capacitor.Cli.Core/Http/BearerRejectionReportHandler.cs[59-65]
src/Capacitor.Models.Transcripts/JsonElementExtensions.cs[5-11]
src/Capacitor.Cli.Core/Capacitor.Cli.Core.csproj[40-44]

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 new 401 error-code parser uses two direct JSON kind comparisons despite available helpers.

## Fix Focus Areas
- src/Capacitor.Cli.Core/Http/BearerRejectionReportHandler.cs[61-65]

## Recommended Fix
Use `doc.RootElement.IsObject` and `error.IsString`, retaining the existing property lookup and return behavior.

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


9. WebSocket rejections lack bearer details ✓ Resolved
Description
Both hub clients install BearerRejectionReportHandler only through HttpMessageHandlerFactory,
which observes HTTP-handler requests rather than the WebSocket upgrade handshake. If negotiate
succeeds but that handshake receives a 401, neither client produces the new bearer-rejection report.
Code

src/Capacitor.Cli/Commands/WatchCommand.cs[R55-56]

+                    options.HttpMessageHandlerFactory = inner =>
+                        new BearerRejectionReportHandler(time, report => Log(time, report)) { InnerHandler = inner };
Relevance

●● Moderate

Possible SignalR transport coverage gap is architectural and lacks decisive repository precedent.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Both hub connections retain default transport selection, but their new reporter runs only when its
wrapped HTTP handler returns a response. The integration test exercises a 401 at negotiate, not a
later WebSocket upgrade.

src/Capacitor.Cli/Commands/WatchCommand.cs[47-59]
src/Capacitor.Cli.Daemon/Services/ServerConnection.cs[222-235]
src/Capacitor.Cli.Core/Http/BearerRejectionReportHandler.cs[16-27]
test/Capacitor.Cli.Tests.Integration/HubBearerRejectionReportTests.cs[27-50]

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 hub reporter observes HTTP-handler responses but misses 401s returned during a WebSocket upgrade after successful negotiation.

## Fix Focus Areas
- src/Capacitor.Cli/Commands/WatchCommand.cs[47-59]
- src/Capacitor.Cli.Daemon/Services/ServerConnection.cs[222-235]
- test/Capacitor.Cli.Tests.Integration/HubBearerRejectionReportTests.cs[27-50]

## Recommended Fix
Add diagnostic handling for rejected WebSocket upgrades on both hub connections, or explicitly limit the stated coverage to HTTP-handler requests. Test a successful negotiate followed by a rejected upgrade.

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


10. Rejected credentials can forge log lines ✓ Resolved
Description
Describe inserts the unvalidated JWT sub and server-supplied error into the report without
escaping control characters. If either value contains a newline, a 401 written to the watcher's log
file can appear as additional log entries.
Code

src/Capacitor.Cli.Core/Http/BearerRejectionReportHandler.cs[R46-48]

+        var expiresIn = exp is { } e ? $" ({(long)(e - now).TotalSeconds}s from now)" : "";
+
+        return $"bearer sub={sub ?? "-"} iat={iat?.ToString("o") ?? "-"} exp={exp?.ToString("o") ?? "-"}{expiresIn}";
Relevance

●● Moderate

Security concern is plausible, but no closely matching log-injection precedent was found.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The handler reads error directly from response JSON and sub from a JWT it explicitly does not
validate, then interpolates both into a report. The watcher writes that report directly to its
redirected log output.

src/Capacitor.Cli.Core/Auth/JwtClaims.cs[5-10]
src/Capacitor.Cli.Core/Http/BearerRejectionReportHandler.cs[30-48]
src/Capacitor.Cli.Core/Http/BearerRejectionReportHandler.cs[59-65]
src/Capacitor.Cli/Commands/WatchCommand.cs[263-272]
src/Capacitor.Cli/Commands/WatchCommand.cs[3711-3712]

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

## Issue description
JWT subjects and server error strings are placed into a single-line log report without escaping, allowing embedded control characters to create apparent log entries.

## Fix Focus Areas
- src/Capacitor.Cli.Core/Http/BearerRejectionReportHandler.cs[30-48]
- src/Capacitor.Cli.Core/Http/BearerRejectionReportHandler.cs[59-65]
- test/Capacitor.Cli.Core.Tests.Unit/Http/BearerRejectionReportHandlerTests.cs[43-76]

## Recommended Fix
Escape or remove control characters and bound the length of server error and subject values before formatting the report. Test subjects and error codes containing newlines.

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


Grey Divider

Context sources
✅ Compliance rules (platform): 64 rules
✅ Cross-repo context — repo relationships
  Explored: repo: kurrent-io/kcap-server (sha: 09e25f8a) — View relationship
  Explored: repo: kurrent-io/kcap-deployments (sha: de9ba18f) — View relationship
Review mode: ⚖️ Balanced: This is a localized but behaviorally significant RFC authentication-header parser change with subtle edge cases that warrant a complete review.

Grey Divider

Tip of the day
💡 Did you know, you can route each severity your way: inline, summary, both, or drop

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread src/Capacitor.Cli.Core/Auth/JwtClaims.cs Outdated
Comment thread src/Capacitor.Cli.Core/Http/BearerRejectionReportHandler.cs Outdated
Comment thread test/Capacitor.Cli.Tests.Integration/HubBearerRejectionReportTests.cs Outdated
Comment thread src/Capacitor.Cli.Core/Http/BearerRejectionReportHandler.cs Outdated
Comment thread src/Capacitor.Cli.Core/Http/BearerRejectionReportHandler.cs Outdated
Comment thread src/Capacitor.Cli/Commands/WatchCommand.cs
realtonyyoung and others added 3 commits September 29, 2026 21:11
Over HTTP/1.1 SignalR opens the WebSocket outside every message handler, so the hubs
take a WebSocket factory that records the upgrade's 401.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@realtonyyoung

Copy link
Copy Markdown
Collaborator Author

/agentic_review

Comment thread src/Capacitor.Cli.Core/Http/BearerRejectionReportHandler.cs Outdated
@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 7ba0575

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@realtonyyoung

Copy link
Copy Markdown
Collaborator Author

/agentic_review

Comment thread src/Capacitor.Cli.Core/Http/BearerRejectionReportHandler.cs Outdated
Comment thread src/Capacitor.Cli.Core/Http/BearerRejectionReportHandler.cs Outdated
@qodo-code-review

Copy link
Copy Markdown

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@realtonyyoung

Copy link
Copy Markdown
Collaborator Author

/agentic_review

Comment thread src/Capacitor.Cli.Core/Http/BearerRejectionReportHandler.cs Outdated
@qodo-code-review

Copy link
Copy Markdown

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@realtonyyoung

Copy link
Copy Markdown
Collaborator Author

/agentic_review

@qodo-code-review

Copy link
Copy Markdown

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

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.

1 participant