Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix(installer): retry backend install with --native-tls on TLS trust failure#1694

Merged
kovtcharov merged 7 commits into
mainfrom
autofix/issue-1693
Jun 17, 2026
Merged

fix(installer): retry backend install with --native-tls on TLS trust failure#1694
kovtcharov merged 7 commits into
mainfrom
autofix/issue-1693

Conversation

@github-actions

Copy link
Copy Markdown
Contributor

Before: on a corporate network that inspects TLS (a MITM proxy with its own root CA), the GAIA backend installer failed every update with error: Failed to fetch: https://pypi.org/simple/amd-gaia/ … invalid peer certificate: UnknownIssuer. uv ships its own bundled webpki root certificates and doesn't trust the proxy's CA, so the install could never complete — and because the error wraps in "Failed to fetch", the installer's transient-network retry burned all three attempts against the same bundled roots. After: the installer detects a TLS trust failure and retries once with --native-tls, which makes uv use the OS certificate store where IT departments install the corporate root CA, so the install succeeds on these networks. If it still fails, the error message now names the cause (untrusted proxy certificate) and the fix (install the CA in the system store) instead of the generic manual-install hint.

Closes #1693

Test plan

  • npx jest tests/electron/backend-installer-lock.test.cjs passes (covers the new isTlsCertError detector against the exact issue [Bug]: GAIA install fails on update #1693 signature)
  • python -m pytest tests/unit/ -x passes
  • python util/lint.py --all passes

⚠️ Needs manual validation — the automated checks here confirm the detector and retry logic, but can't reproduce a real corporate TLS-intercepting proxy. A maintainer on a network with a MITM proxy (or simulating one) should confirm that gaia chat --ui completes a backend install/update that previously failed with invalid peer certificate: UnknownIssuer, and that the install log shows the --native-tls retry succeeding.

…failure

A corporate MITM proxy presents PyPI with a certificate signed by a custom
root CA that uv's bundled webpki roots don't include, so the install fails
with "invalid peer certificate: UnknownIssuer". It surfaces wrapped in
"Failed to fetch", so the existing transient-network retry burned all three
attempts against the same bundled roots. Detect the cert error first and
retry once with --native-tls, which makes uv trust the OS certificate store
where IT installs the corporate CA.

Closes #1693
@kovtcharov-amd kovtcharov-amd marked this pull request as ready for review June 16, 2026 20:22
@kovtcharov-amd kovtcharov-amd self-requested a review as a code owner June 16, 2026 20:22
@github-actions

Copy link
Copy Markdown
Contributor Author

Summary

Clean, well-scoped fix for issue #1693. It correctly identifies that uv's bundled webpki roots can't validate a corporate MITM proxy's CA, that the resulting UnknownIssuer error masquerades as a transient "Failed to fetch", and that the right remedy is a --native-tls retry against the OS trust store. The detector is checked before the transient-retry path so it doesn't burn all three attempts on the same doomed roots — the exact failure mode the PR description calls out. Code, comments, tests, and the actionable error message all line up. The one thing worth knowing: there's a narrow ordering gap where the --native-tls retry can be skipped entirely (details below), but it won't bite the real-world case this PR targets.

Issues Found

🟢 --native-tls retry is skipped if the TLS signature first appears on the final attempt (backend-installer.cjs:1232)

The TLS branch sets useNativeTls = true and continues, which consumes a loop iteration rather than re-running in place. If attempts 1–2 fail for genuinely transient reasons and the UnknownIssuer signature only surfaces on attempt 3 (INSTALL_MAX_ATTEMPTS), the continue increments past the loop bound and the --native-tls attempt never executes — the install throws with the (correct) TLS suggestion, but the actual retry the PR promises didn't happen.

In practice a TLS-intercepting proxy fails deterministically from attempt 1, so this is unlikely to ever fire — hence minor, not blocking. If you want to close it cleanly, gate the loop bound so a not-yet-tried --native-tls always gets one real attempt, e.g.:

  for (let attempt = 1; attempt <= INSTALL_MAX_ATTEMPTS || (useNativeTls && !nativeTlsAttempted); attempt++) {

(with a nativeTlsAttempted flag set when the native-TLS run actually executes) — or simply re-run inline instead of continue. Your call; the current behavior is acceptable for the targeted scenario.

🟢 Consider noting the redundant native-TLS retries via the transient path (backend-installer.cjs:1245)

Once useNativeTls is set, a subsequent TLS failure still matches isTransientNetworkError (it wraps in "Failed to fetch"), so the remaining attempts re-run with --native-tls under the transient backoff. That's harmless — arguably a bonus extra try — but the "retry once with --native-tls" comment at line 1230 slightly undersells it. Not worth changing code over; just flagging that the comment and runtime behavior diverge a little.

Strengths

  • Correct root-cause framing. The ordering — check isTlsCertError before isTransientNetworkError — is the crux of the fix, and the comments at backend-installer.cjs:1228 and 497522 explain why (the transient matcher also catches this) rather than restating the code. This is exactly the WHY-comment style CLAUDE.md asks for.
  • Fails loudly with an actionable message. The new else if (isTlsCertError(output)) branch names what failed (untrusted proxy cert), what the user should do (ask IT to install the CA in the system store), and where to go next (quickstart link) — matches the "actionable errors name three things" rule, no silent fallback.
  • Tests target the real signature. isTlsCertError is exported and covered against the exact issue [Bug]: GAIA install fails on update #1693 UnknownIssuer block plus common phrasings, with negative cases asserting plain network/dependency failures don't match. Honest about its limits, too — the PR flags that automated checks can't reproduce a live MITM proxy and asks for manual validation.

Verdict

Approve. No blocking issues. The two minor notes are optional polish; the change is correct, well-tested, and safe to merge for the scenario it targets. Worth honoring the PR's own call-out for one manual validation on a real (or simulated) TLS-intercepting proxy before relying on it in the field.

@github-actions

Copy link
Copy Markdown
Contributor Author

🟡 backend-installer.cjs:1272 — error message can claim --native-tls was tried when it wasn't

If the TLS error only surfaces on the last attempt (e.g. attempts 1–2 failed with transient errors and used up INSTALL_MAX_ATTEMPTS - 1 slots), the !useNativeTls && isTlsCertError(...) branch fires, sets useNativeTls = true, and continues — but the loop counter has already hit INSTALL_MAX_ATTEMPTS, so the loop exits without ever running the --native-tls retry. The failure path then shows "GAIA retried with the OS certificate store and it still failed", which is false.

Fix: gate the user-facing message on whether native-TLS was actually executed:

} else if (isTlsCertError(output)) {
  suggestion = useNativeTls
    ? "PyPI's certificate isn't trusted on this network — usually a corporate proxy. " +
      "GAIA retried with the OS certificate store and it still failed. " +
      "Ask IT to install the proxy's root CA in your system's trusted certificate store, then relaunch GAIA. " +
      "See https://amd-gaia.ai/quickstart#cli-install"
    : "PyPI's certificate isn't trusted on this network — usually a corporate proxy. " +
      "Try rerunning; if that also fails, ask IT to install the proxy's root CA. " +
      "See https://amd-gaia.ai/quickstart#cli-install";
}

The happy path (TLS error on attempt 1, native-tls succeeds or gives a clear second failure) is unaffected.

Addresses review feedback on the issue #1693 TLS fix. The TLS branch
flips useNativeTls and continues, consuming a loop iteration; if the
UnknownIssuer signature only surfaced on the final attempt (e.g. after
two transient failures), the loop exited without ever running the
--native-tls retry — and the failure message still claimed GAIA had
retried against the OS trust store. Extend the loop bound by one when a
not-yet-run --native-tls is pending, and gate that message on whether
the retry actually executed.
@kovtcharov

Copy link
Copy Markdown
Collaborator

Addressed the review feedback in d1c39f5 — the ordering gap where --native-tls could be skipped.

The TLS branch flips useNativeTls and continues, consuming a loop iteration. If the UnknownIssuer signature only surfaced on the final attempt (two transient failures first), the loop exited without ever running the retry — and the failure message still claimed GAIA had retried against the OS trust store. Fix:

  • Extend the loop bound by one when a not-yet-run --native-tls is pending (nativeTlsAttempted flag), so the promised retry always gets one real attempt.
  • Gate the "GAIA retried with the OS certificate store" line on whether the retry actually executed, so the message can't claim a retry that didn't happen.

Traced the edge case (transient×2 → TLS on attempt 3 now runs a real --native-tls attempt) and confirmed the extension fires at most once (no infinite loop). The predicate suite still passes; the install loop itself isn't exported, so it has no direct unit test without refactoring the bootstrap function.

github-merge-queue Bot pushed a commit that referenced this pull request Jun 17, 2026
The Electron framework test suite is silently broken on `main`: any PR
touching a `webui/` path fails CI with
`expect(msgCss).toContain('border-left: 2px solid var(--amd-red)')`,
even when unrelated to the UI (e.g. #1694, an installer fix). This
unblocks those PRs.

Root cause: the Agent Hub redesign (#1564) renamed the error message's
left-border token from `--amd-red` to `--danger` in `MessageBubble.css`
but didn't update the CSS-assertion test that pins the old token.
`main`'s own Electron CI is path-filtered and hadn't re-run since the
rename landed, so it stayed latent until a webui-touching PR triggered
it. Same class of issue as #1701.

## Test plan
- [ ] `cd tests/electron && npm test` — all suites pass (was 1 failed)
- [ ] `npx jest test_electron_chat_app.js` — the previously-failing
assertion passes
The Agent Hub redesign renamed the error message's left-border token
from --amd-red to --danger in MessageBubble.css; this webui-touching PR
trips the stale CSS assertion in the Electron suite. Same fix as #1704 —
included here so this PR's CI is green independently.
itomek
itomek previously approved these changes Jun 17, 2026
@itomek itomek added this pull request to the merge queue Jun 17, 2026
@itomek itomek removed this pull request from the merge queue due to a manual request Jun 17, 2026
# Conflicts:
#	src/gaia/apps/webui/services/backend-installer.cjs
@kovtcharov kovtcharov added this pull request to the merge queue Jun 17, 2026
Merged via the queue into main with commit cfd2e41 Jun 17, 2026
59 checks passed
@kovtcharov kovtcharov deleted the autofix/issue-1693 branch June 17, 2026 19:38
pull Bot pushed a commit to bhardwajRahul/gaia that referenced this pull request Jun 17, 2026
The Agent UI Vitest suite was silently broken on `main`: any PR touching
a `webui/` path (e.g. amd#1694, an installer fix) fails CI with `TypeError:
Cannot read properties of undefined (reading 'then')` in
`ChatView.test.tsx`, even when the PR has nothing to do with the UI.
This unblocks those PRs.

Root cause: amd#1656 added an `api.getActiveRuns().then(...)` call to
`ChatView`'s mount effect but never stubbed `getActiveRuns` in the
test's auto-mocked api module, so it returned `undefined`. `main`'s own
Vitest is path-filtered and hadn't re-run since the regression landed,
so it stayed latent until a webui-touching PR triggered it.

## Test plan
- [ ] `cd src/gaia/apps/webui && npx vitest run` — all 62 tests pass
- [ ] `npx vitest run src/components/__tests__/ChatView.test.tsx` — the
previously-failing test passes
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

tests Test changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: GAIA install fails on update

3 participants