Skip to content

🧪 Harden solution-server test teardown and auth-manager refresh timer - #1472

Open
ibolton336 wants to merge 4 commits into
konveyor:mainfrom
ibolton336:fix/ci-infra-test-stability
Open

🧪 Harden solution-server test teardown and auth-manager refresh timer#1472
ibolton336 wants to merge 4 commits into
konveyor:mainfrom
ibolton336:fix/ci-infra-test-stability

Conversation

@ibolton336

@ibolton336 ibolton336 commented Jul 8, 2026

Copy link
Copy Markdown
Member

Summary

  • Delay health poll reconnectSolutionServer() until 2+ consecutive failures — a single transient blip no longer triggers a full reconnect cycle that creates new MCP transports through the proxy
  • Guard disconnect() calls on partially-connected clients with isConnected check — disconnect is only meaningful when the MCP session was actually established
  • Guard afterAll in analysis-validation.test.ts with optional chaining to prevent cascading TypeError when beforeAll fails

Context

After #1468 merged, getServerCapabilities() now throws on connection errors (previously returned empty capabilities). This causes the health poll to call reconnectSolutionServer() on every failure — each call creates a new MCP transport connection through the nginx ingress. On minikube, this connection churn destabilizes the ingress, causing HTTP 421 Misdirected Request errors for other clients.

Test plan

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved token refresh handling to avoid timer overflows when refresh delays are extremely large.
    • Made test cleanup more resilient by safely skipping shutdown when the app instance is unavailable.
  • Chores

    • Updated environment setup checks to wait for the API service to become fully ready before continuing, reducing flaky startup failures.

@ibolton336
ibolton336 requested review from a team as code owners July 8, 2026 20:06
The health poll's reconnectSolutionServer() fired on the very first
capability-check failure, creating a new MCP transport each time. On
minikube's nginx ingress this connection churn increases flakiness —
the ingress can return 421 Misdirected Request to other clients
connecting through the same proxy.

- Delay reconnection until 2+ consecutive poll failures so a single
  transient blip doesn't trigger a full reconnect cycle
- Guard disconnect() calls on partially-connected clients with
  isConnected — disconnect only makes sense when the MCP session was
  actually established (e.g. transport up but listTools failed)
- Guard afterAll in analysis-validation.test.ts with optional chaining
  to prevent cascading TypeError when beforeAll fails

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: ibolton336 <ibolton@redhat.com>
@ibolton336
ibolton336 force-pushed the fix/ci-infra-test-stability branch from b4d8528 to 7028cf3 Compare July 8, 2026 20:08
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds a health-check workaround to the infrastructure setup action, disabling a host origin protection flag on the kai-api Deployment and polling its ingress-routed MCP JSON-RPC endpoint with retries before failing and printing deployment logs. It also adds an overflow guard to the authentication manager's auto-refresh timer scheduling to prevent excessive setTimeout delays, and makes a test's VSCode cleanup call null-safe.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers: fabianvf, djzager

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Title check ✅ Passed The title is clear and mostly matches the changes, though it omits the infra setup-action work.
Description check ✅ Passed The description has Summary, Context, and Test plan sections and is sufficiently complete for the template.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
vscode/core/src/extension.ts (1)

758-775: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Successful reconnect's 10s poll interval is immediately overwritten to 60s.

On a successful reconnect you set consecutiveFailures = 0 and pollInterval = 10000, but execution then falls through to the backoff block. With consecutiveFailures === 0, the else if (consecutiveFailures < 5) branch runs and resets pollInterval = 60000, so the recovered connection is polled every 60s instead of the intended 10s.

Short-circuit after a successful reconnect so the backoff block is skipped:

🐛 Proposed fix
             if (consecutiveFailures >= 2) {
               const reconnected = await this.state.hubConnectionManager.reconnectSolutionServer();
               if (reconnected) {
                 consecutiveFailures = 0;
                 pollInterval = 10000;
                 this.state.mutateServerState((draft) => {
                   draft.solutionServerConnected = true;
                 });
+                scheduleNextPoll(withJitter(pollInterval));
+                return;
               }
             }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@vscode/core/src/extension.ts` around lines 758 - 775, The reconnect handling
in extension.ts is letting the backoff logic overwrite the intended fast poll
after a successful reconnect. In the block around
this.state.hubConnectionManager.reconnectSolutionServer(), short-circuit once
reconnected is true so the later consecutiveFailures-based pollInterval
assignment is skipped, preserving the 10000ms interval and the
solutionServerConnected state update.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@vscode/core/src/extension.ts`:
- Around line 758-775: The reconnect handling in extension.ts is letting the
backoff logic overwrite the intended fast poll after a successful reconnect. In
the block around this.state.hubConnectionManager.reconnectSolutionServer(),
short-circuit once reconnected is true so the later consecutiveFailures-based
pollInterval assignment is skipped, preserving the 10000ms interval and the
solutionServerConnected state update.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d00cb2b7-a7b6-40a5-909f-903974481c07

📥 Commits

Reviewing files that changed from the base of the PR and between 9ef1343 and b4d8528.

📒 Files selected for processing (3)
  • tests/e2e/tests/solution-server/analysis-validation.test.ts
  • vscode/core/src/extension.ts
  • vscode/core/src/hub/HubConnectionManager.ts

The infra tests fail because the kai-api MCP endpoint isn't routable
through the nginx ingress when tests start — the deployment is marked
available but the ingress route takes additional time to propagate.

- Add a kai-api MCP endpoint health check to setup-konveyor-infrastructure
  that polls until the endpoint responds (non-421/502/503)
- Add retry logic (3 attempts, 5s delay) to MCPClient.connect() for
  transient 421 Misdirected Request errors
- Fix TimeoutOverflowWarning in the test's AuthenticationManager — tokens
  with multi-year lifespans overflowed setTimeout's 32-bit int limit

Signed-off-by: ibolton336 <ibolton@redhat.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (2)
tests/mcp-client/mcp-client.model.ts (1)

97-110: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Clean up partially-initialized transport before retrying.

When connectTransport() throws on a 421, this.transport and this.client are already assigned. On the next retry, connectTransport() overwrites them without closing the old transport, orphaning it. Compare with reconnectWithNewToken() (line 142) which properly calls this.transport.close() before replacing it.

♻️ Proposed fix: close orphaned transport on retry
        if (!is421 || attempt === MAX_RETRIES) {
          throw error;
        }
+       // Clean up partially-initialized transport/client from the failed attempt
+       if (mcpClient.transport) {
+         await mcpClient.transport.close().catch(() => {});
+       }
+       mcpClient.client = undefined;
+       mcpClient.transport = undefined;
        console.warn(
          `MCP connect attempt ${attempt}/${MAX_RETRIES} got 421, retrying in ${RETRY_DELAY_MS}ms...`
        );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/mcp-client/mcp-client.model.ts` around lines 97 - 110, The retry loop
in connectTransport() leaves a partially initialized transport/client behind
when a 421 triggers a retry. Before the next retry attempt, explicitly clean up
the existing this.transport (and related client state if needed) so the old
transport is not orphaned. Mirror the cleanup behavior already used in
reconnectWithNewToken() by closing the current transport before assigning a new
one, then continue with the retry delay and reconnect logic.
tests/solution-server-auth/authentication-manager.ts (1)

63-68: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Guard is correct; consider aligning with the production re-evaluation approach.

The early-return guard correctly prevents setTimeout overflow for oversized delays. After the return, this.refreshTimer is already null (cleared on line 59), so no stale timer leaks.

One difference worth noting: the production HubConnectionManager (lines 1445–1469) schedules a MAX_TIMER_MS re-evaluation when the delay exceeds the limit, while this test utility simply returns. For test infrastructure this is acceptable since tokens with multi-year lifespans won't realistically expire during a test run, and ensureAuthenticated will refresh on-demand when needed. If you want consistency, consider adding a log line when the guard triggers to aid debugging unexpected test behavior.

Based on learnings from cross-file context in vscode/core/src/hub/HubConnectionManager.ts:1445-1469.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/solution-server-auth/authentication-manager.ts` around lines 63 - 68,
The oversized-delay guard in authentication-manager’s refresh scheduling is
fine, but it diverges from HubConnectionManager’s MAX_TIMER_MS handling. If you
want parity, update the refresh path around the timer setup so delays above the
limit are re-evaluated instead of only returning, or at minimum add a debug log
when the guard in the refresh scheduling logic triggers to make unexpected test
behavior easier to diagnose.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tests/mcp-client/mcp-client.model.ts`:
- Around line 97-110: The retry loop in connectTransport() leaves a partially
initialized transport/client behind when a 421 triggers a retry. Before the next
retry attempt, explicitly clean up the existing this.transport (and related
client state if needed) so the old transport is not orphaned. Mirror the cleanup
behavior already used in reconnectWithNewToken() by closing the current
transport before assigning a new one, then continue with the retry delay and
reconnect logic.

In `@tests/solution-server-auth/authentication-manager.ts`:
- Around line 63-68: The oversized-delay guard in authentication-manager’s
refresh scheduling is fine, but it diverges from HubConnectionManager’s
MAX_TIMER_MS handling. If you want parity, update the refresh path around the
timer setup so delays above the limit are re-evaluated instead of only
returning, or at minimum add a debug log when the guard in the refresh
scheduling logic triggers to make unexpected test behavior easier to diagnose.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2f0a250f-85d6-4836-a69b-167b950db794

📥 Commits

Reviewing files that changed from the base of the PR and between b4d8528 and d8e6742.

📒 Files selected for processing (6)
  • .github/actions/setup-konveyor-infrastructure/action.yml
  • tests/e2e/tests/solution-server/analysis-validation.test.ts
  • tests/mcp-client/mcp-client.model.ts
  • tests/solution-server-auth/authentication-manager.ts
  • vscode/core/src/extension.ts
  • vscode/core/src/hub/HubConnectionManager.ts
✅ Files skipped from review due to trivial changes (1)
  • tests/e2e/tests/solution-server/analysis-validation.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • vscode/core/src/hub/HubConnectionManager.ts
  • vscode/core/src/extension.ts

@ibolton336 ibolton336 changed the title 🐛 Fix health poll reconnection churn destabilizing infra tests 🌱 Stabilize @requires-minikube infra tests: mitigate kai-api 421, add MCP readiness gate Jul 9, 2026
@ibolton336

Copy link
Copy Markdown
Member Author

Root cause found — this PR now fixes it at the source

Heads up for reviewers: the premise of the earlier commits on this branch turned out to be wrong, so the PR has been re-scoped and retitled (:bug::seedling:).

What actually broke the @requires-minikube infra tests: not reconnect churn or client-side flakiness. The kai-solution-server:latest image resolves fastmcp from a floor (>=2.8.0) at build time (Containerfile installs from pyproject.toml, never the lock), so recent rebuilds pull FastMCP 3.4.3+, which enables host/origin (DNS-rebinding) protection by default with an empty allowlist. Behind the tackle-hub ingress the forwarded Host header is the external IP, which never matches, so every request to /hub/services/kai/api gets 421 Misdirected Request before it reaches the MCP session layer. Deterministic, not flaky. Tracked upstream in konveyor/kai#934.

What this PR now does:

  • Setup action disables FASTMCP_HTTP_HOST_ORIGIN_PROTECTION on the kai-api deployment (documented escape hatch), then gates on a real kai-api MCP readiness probe before tests run — so a future recurrence fails fast with kai-api logs instead of mysterious test failures.
  • Reverts the earlier extension.ts / HubConnectionManager.ts health-poll and disconnect edits. They were built on the disproven "reconnect churn destabilizes the proxy" theory, and the poll debounce needlessly delayed the stale-connection auto-recovery that 🐛 Update solution server status on stale MCP connection loss #1468 just added. No shipped extension behavior changes here.
  • Drops the now-moot 421 retry from the test MCP client.

CI-only change, hence :seedling: and no changelog fragment. The env-var mitigation is temporary — it can be removed once kai#934 pins a safe fastmcp version in the image.

The @requires-minikube infra tests broke because the kai-solution-server
:latest image resolves fastmcp from a floor (>=2.8.0) at build time and
now pulls FastMCP 3.4.3+, which enables host/origin (DNS-rebinding)
protection by default with an empty allowlist. Behind the tackle-hub
ingress the forwarded Host header never matches, so every request to
/hub/services/kai/api is rejected with 421 before reaching the MCP
session layer. Tracked upstream in konveyor/kai#934.

- setup action: disable FASTMCP_HTTP_HOST_ORIGIN_PROTECTION on kai-api,
  then gate on a real kai-api MCP readiness probe before tests run
- revert the earlier extension.ts / HubConnectionManager health-poll and
  disconnect changes: they were based on a "reconnect churn destabilizes
  the proxy" theory that the 421 root cause disproves, and the poll
  debounce needlessly delayed konveyor#1468's stale-connection auto-recovery
- drop the now-moot 421 retry from the test MCP client

This is a CI/test-stability change only; no shipped extension behavior
changes, hence the 🌱 prefix and no changelog fragment.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: ibolton336 <ibolton@redhat.com>
@ibolton336
ibolton336 force-pushed the fix/ci-infra-test-stability branch from 5a0585f to 5b670dc Compare July 9, 2026 00:07

@coderabbitai coderabbitai 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/actions/setup-konveyor-infrastructure/action.yml (1)

342-360: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

|| echo "000" produces "000000" on curl failure, bypassing the readiness gate.

When curl fails (connection refused, timeout, DNS failure), %{http_code} already outputs "000" to stdout and curl returns non-zero. The || echo "000" then appends a second "000", so KAI_STATUS becomes "000000". This value matches none of the denylist entries (421/502/503/000), so the loop breaks on the first iteration and reports the endpoint as ready — defeating the entire readiness gate for exactly the failure modes it's meant to catch.

Replace || echo "000" with || true; curl's -w "%{http_code}" already emits "000" on failure, so the denylist check works correctly without the duplicate output.

🐛 Proposed fix
         for i in $(seq 1 30); do
           KAI_STATUS=$(curl -k -sS --connect-timeout 5 --max-time 15 \
             -o /dev/null -w "%{http_code}" \
             -X POST "${HUB_URL}/hub/services/kai/api" \
             -H "Authorization: Bearer ${APIKEY}" \
             -H "Content-Type: application/json" \
             -d '{"jsonrpc":"2.0","method":"initialize","id":0,"params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"health-check","version":"0.0.0"}}}' \
-            || echo "000")
+            || true)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/actions/setup-konveyor-infrastructure/action.yml around lines 342 -
360, The readiness check in the kai-api probe is appending a duplicate failure
code because `KAI_STATUS` is assigned from the `curl` command in the
health-check loop. Remove the `|| echo "000"` fallback and let `curl -w
"%{http_code}"` provide the `"000"` status on failure, so the existing denylist
check against `KAI_STATUS` correctly keeps retrying instead of breaking early.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In @.github/actions/setup-konveyor-infrastructure/action.yml:
- Around line 342-360: The readiness check in the kai-api probe is appending a
duplicate failure code because `KAI_STATUS` is assigned from the `curl` command
in the health-check loop. Remove the `|| echo "000"` fallback and let `curl -w
"%{http_code}"` provide the `"000"` status on failure, so the existing denylist
check against `KAI_STATUS` correctly keeps retrying instead of breaking early.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: dfe41051-decf-471a-92e0-c4c84d1a6f9f

📥 Commits

Reviewing files that changed from the base of the PR and between d8e6742 and 5a0585f.

📒 Files selected for processing (1)
  • .github/actions/setup-konveyor-infrastructure/action.yml

The FASTMCP_HTTP_HOST_ORIGIN_PROTECTION=false mitigation on the kai-api
deployment does not hold: the deployment is operator-managed, so the
konveyor operator reconciles the env var back to the CR spec, and the
readiness probe gave a false green (it omits the MCP streamable Accept
header, so it never exercised the real 421 path).

The real cause is that the kai-solution-server image installs from the
pyproject floor (fastmcp>=2.8.0) instead of its own lockfile, so it
drifted onto fastmcp 3.4.3 whose HostOriginGuardMiddleware defaults on
and 421s every proxied request. Fixed upstream in konveyor/kai#934.

This leaves only the genuine, root-cause-independent test hardening on
this branch (afterAll teardown guard + auth-manager timer overflow).

Signed-off-by: ibolton336 <ibolton@redhat.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ibolton336 ibolton336 changed the title 🌱 Stabilize @requires-minikube infra tests: mitigate kai-api 421, add MCP readiness gate 🧪 Harden solution-server test teardown and auth-manager refresh timer Jul 9, 2026
@ibolton336

Copy link
Copy Markdown
Member Author

Re-scoped: the CI workaround was reverted — the fix belongs in kai

The FASTMCP_HTTP_HOST_ORIGIN_PROTECTION=false mitigation I pushed earlier was confirmed not to work (E2E run still 421'd on both infra tests), and I've reverted it. Two reasons it couldn't hold:

  1. The kai-api deployment is operator-managed — the konveyor operator reconciles it, so a kubectl set env on the Deployment gets reverted back to the CR spec.
  2. The readiness probe gave a false green — it omits the MCP streamable Accept: text/event-stream header, so it got a non-421 response and declared the endpoint ready; that's why the tests ran (and then failed) instead of setup failing loudly.

Confirmed root cause: the kai-solution-server image installs from the pyproject.toml floor (fastmcp>=2.8.0) instead of its lockfile (fastmcp==2.14.3), so it drifted onto fastmcp 3.4.3, whose HostOriginGuardMiddleware defaults on and returns 421 Misdirected Request for every proxied request. (fastmcp 3.4.4 already flipped that default back to False.) The durable fix is in the kai image — tracked in konveyor/kai#934, PR incoming.

What's left on this PR is only root-cause-independent test hardening, hence the retitle to :test_tube::

  • analysis-validation.test.ts: vsCode?.closeVSCode() in afterAll so a teardown TypeError can't mask the real failure.
  • authentication-manager.ts: guard the token-refresh setTimeout against 32-bit overflow (multi-year tokens were wrapping to ~1ms and spinning the refresh loop).

@requires-minikube will stay red until kai#934 ships a fixed image; that's expected and out of scope for this PR.

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.

2 participants