When POST /api/cliproxy/auth/:provider/start-url returns 400
plus_oauth_credentials_missing or 502 plus_oauth_url_missing_client_id,
surface the server-provided human-readable message (env var names and
backend=original switch hint) instead of the raw error code. Error
propagates through the existing toast/inline-alert path in
add-account-dialog.tsx — no new component primitives.
Refs #1208
The dashboard Add Account flow hits POST /api/cliproxy/auth/:provider/start-url
which fetches the OAuth consent URL directly from the CLIProxy Plus
management API. With cliproxy.backend=plus and unset
CLIPROXY_{GEMINI,ANTIGRAVITY}_OAUTH_CLIENT_ID/SECRET, Plus returns a URL
with empty client_id= and Google rejects with 400 invalid_request. PR
#1131 only guarded the CLI /start path; /start-url was unguarded.
- Pre-fetch guard via getPlusOAuthCredentialError -> 400
plus_oauth_credentials_missing before contacting the Plus binary.
- Post-fetch guard via getPlusAuthUrlCredentialError -> 502
plus_oauth_url_missing_client_id if Plus still emits a URL without
client_id (logged with query string redacted).
- New integration test (cliproxy-auth-routes-oauth-guard.test.ts) covers
gemini/agy guard firing, non-table providers passing through, and both
error body contracts. 15 cases pass.
Closes#1208
Refactor getGeminiPlusOAuthCredentialError / getGeminiAuthUrlCredentialError
behind a provider-table-driven helper (PLUS_OAUTH_ENV_BY_PROVIDER) and add
getPlusOAuthCredentialError / getPlusAuthUrlCredentialError exports covering
both gemini and agy. Existing gemini-named exports preserved as aliases so
PR #1131 test surface remains unchanged. New AGY-parity test cases mirror
the original gemini diagnostics. CLI call sites in triggerOAuth /
handlePasteCallbackMode stay gemini-only; dashboard handler in the
follow-up commits will use the generalized exports for both providers.
Refs #1208
Two pre-existing test bugs surfaced under CCS CI on the new claw self-
hosted runners but stayed hidden locally:
1. tests/unit/commands/cliproxy-routing-subcommand.test.ts mocks the
routing-subcommand module with only 3 exports, but
src/commands/cliproxy/index.ts statically imports 6. Bun resolves
the static import graph against the mock and reports
SyntaxError: Export named 'handleRoutingAffinitySet' not found
for every test in the file. Add the missing 3 exports
(handleRoutingAffinityStatus, handleRoutingAffinityHelp,
handleRoutingAffinitySet) to the mock and document why the mock
must mirror every named export of the target module.
2. tests/unit/hooks/websearch-transformer.test.ts builds the
"disallowed" trace path from process.cwd() and from os.homedir().
Both fall under the os.tmpdir() safe-prefix in two real
environments: CI runners with cwd == /tmp/runner/work/... and Bun
test isolation that re-roots HOME under tmpdir. The hook treats
them as safe, writes the trace, and the assertion that the file
does NOT exist fails. Anchor disallowedTracePath under /etc/...
instead so it cannot satisfy the tmpdir, /var/log, or
<CCS_HOME>/.ccs/logs prefixes in any host environment.
Both fixes are independent of the OAuth callback traceability change
that this branch otherwise carries, but ship together so the PR
clears CI on the new runner stack.
This reverts the runs-on change for ai-review.yml. All CI/CD must
stay on home infra (claw, docker LXC, etc); GitHub-hosted runners
are forbidden. The qodo-ai/pr-agent docker action mount issue will
be addressed by either configuring the self-hosted runner image to
use path-identical bind mounts so nested-docker volume sources
resolve correctly, or by switching to native CLI invocation
(pip install pr-agent) instead of the docker action.
Tracked separately; not blocking PR #1209.
The qodo-ai/pr-agent Docker action requires the GitHub event payload
at /github/workflow/event.json inside the action container. On
self-hosted runners that themselves run inside a Docker container
(e.g. myoung34/github-runner), the host docker daemon resolves the
volume mount /tmp/runner/work/_temp/_github_workflow:/github/workflow
against the host filesystem, where the runner's /tmp path does not
exist. The action container starts with an empty /github/workflow
mount and fails with FileNotFoundError on event.json.
AI review jobs do not need self-hosted runner access (no bun cache,
no internal infra). Switch both dispatch-review and pr-agent jobs
to ubuntu-latest so the volume mount resolves on the same host where
the action expects it.