Skip to content

Report the workspaces discovery finds, without choosing one - #1005

Merged
alexeyzimarev merged 10 commits into
mainfrom
georgepayne/ai-2935-setup-discover
Sep 18, 2026
Merged

alexeyzimarev merged 10 commits into
mainfrom
georgepayne/ai-2935-setup-discover

Conversation

@George-Payne

@George-Payne George-Payne commented Sep 18, 2026 •

Copy link
Copy Markdown
Member

AI-2935 — no GitHub issue exists for this work

What & why

kcap setup --discover [--json] signs in, reports the workspaces the account can reach, and stops. That list only exists on the far side of a sign-in, and setup learns it in the middle of a run it then carries to completion, so a tool that has to ask someone which workspace to use had no command to ask with.

It reaches the rows through the proxy directly rather than through the choosing flow, which selects a sole workspace before any picker is asked. Nothing is published — no profile, no activation, no token — so the run that follows signs in again; on a device-code flow that is a second approval.

Where to look

  • can_create means no workspace was found on the one lane that can create one. It is not a promise: a workspace already being made for the account is only learned by the create call.
  • --discover takes a closed set (--json, --github, --device, --no-prompt); a workspace argument or any other option is refused by name.
  • kcap login --discover keeps its own meaning: pick a workspace and save it.

Verification

  • OnboardingFacadeDiscoverOnlyTests: one, several and zero workspaces leave the config root empty, beside a control that publishes the same script through the choosing route.
  • Core unit suite 3,411 passed, CLI unit suite 4,333 passed, 0 failed in either.
  • Published AOT binary: kcap setup --discover acme and kcap setup --discover --plugin-scope user exit 1 with their refusal before any sign-in; the publish shows no IL2026/IL3050.
  • Not covered: a complete --discover run against a live tenant, which needs an approval in a browser.

🤖 Generated with Claude Code

The workspaces an account belongs to only exist on the far side of a sign-in,
and setup learns them mid-run and carries on to completion. A tool that has to
ask someone which workspace to use has nowhere to stand: the choice has to be
made before the only command that could inform it.

`kcap setup --discover [--json]` signs in, reports what it found, and stops.
It changes nothing, which is the point rather than a detail: the picker
declines, a decline is strictly pre-boundary, and the boundary is where
profiles, tenant activation and tokens are published together. No provisioner
is supplied, so the route cannot create a workspace either.

Publishing nothing means the token is not published, so the run that follows
signs in again. That is the trade for a report that cannot leave a machine
half-configured. Naming a server alongside --discover is refused rather than
ignored: it already answers the question discovery exists to ask.
@linear-code

linear-code Bot commented Sep 18, 2026

Copy link
Copy Markdown

AI-2935

@qodo-code-review

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

Copy link
Copy Markdown

PR Summary by Qodo

Add non-mutating workspace discovery to setup

✨ Enhancement 🧪 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Add setup --discover to report reachable workspaces without selecting or configuring one.
• Support human-readable and snake_case JSON output while keeping authentication narration off
 stdout.
• Document provider-specific creation eligibility and the deliberate second-sign-in tradeoff.
Diagram

sequenceDiagram
    actor Caller
    participant Setup as Setup Command
    participant Facade as Onboarding Facade
    participant Auth as Auth Provider
    participant Proxy as Auth Proxy
    participant Render as Report Renderer
    Caller->>Setup: setup --discover
    Setup->>Facade: DiscoverOnlyAsync
    Facade->>Auth: Authenticate account
    Auth-->>Facade: Ephemeral token
    Facade->>Proxy: List workspaces
    Proxy-->>Facade: Workspace rows
    Facade-->>Setup: Discovery report
    Setup->>Render: Build output
    Render-->>Caller: Human text or JSON
    Note over Facade,Setup: Commit boundary is never entered
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Reuse normal discovery with a declining picker
  • ➕ Reuses the existing provider orchestration and selection flow.
  • ➕ Requires less provider-specific discovery code in the facade.
  • ➖ A single workspace is selected before the picker runs, potentially configuring the machine.
  • ➖ An empty workspace list is treated as failure instead of a valid report.
2. Persist the discovery token for the next setup run
  • ➕ Avoids a second browser or device-code approval.
  • ➕ Could make discovery followed by setup faster.
  • ➖ Splits the atomic profile, activation, and token publication boundary.
  • ➖ Leaves credentials without a selected workspace and introduces cleanup and security concerns.

Recommendation: Keep the PR's direct, read-only discovery path. It correctly reports zero, one, or many workspaces while never entering the publication boundary or invoking provisioning; the duplicated provider dispatch is preferable to accidental configuration or partially persisted authentication state.

Files changed (13) +347 / -19

Enhancement (7) +222 / -12
AuthProgress.csAllow authentication narration to target a selected stream +20/-9

Allow authentication narration to target a selected stream

• Makes 'ConsoleAuthProgress' accept a 'TextWriter' and adds an stderr factory. All user-facing authentication progress now uses the selected writer while errors remain on stderr.

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

AuthResult.csIntroduce a read-only discovery report model +17/-0

Introduce a read-only discovery report model

• Adds 'DiscoveryReport' for workspace rows, provider, creation eligibility, and failures without implying that authentication state was published.

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

OnboardingFacade.csAdd provider-specific discovery without publication +70/-0

Add provider-specific discovery without publication

• Adds 'DiscoverOnlyAsync' to authenticate and list WorkOS or GitHub App workspaces without selecting, provisioning, or committing state. It derives workspace creation eligibility only for an empty WorkOS result.

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

IOnboardingFacadeFactory.csSupport caller-selected authentication progress sinks +4/-1

Support caller-selected authentication progress sinks

• Extends facade creation with an optional 'IAuthProgress', allowing commands with machine-readable stdout to redirect sign-in narration.

src/Capacitor.Cli/Commands/IOnboardingFacadeFactory.cs

SetupCommand.csRoute and render setup discovery-only requests +67/-0

Route and render setup discovery-only requests

• Adds early '--discover' routing, rejects explicit server combinations, performs read-only discovery, and renders either JSON or human guidance. JSON mode keeps stdout clean by sending sign-in narration to stderr.

src/Capacitor.Cli/Commands/SetupCommand.cs

SetupDiscoverJson.csDefine and serialize the discovery JSON payload +41/-0

Define and serialize the discovery JSON payload

• Adds source-generated snake_case JSON models and a pure renderer. Blank GitHub App slugs and display names are normalized to null while creation eligibility comes from the discovery report.

src/Capacitor.Cli/Commands/SetupDiscoverJson.cs

SetupFacadeFactory.csWire optional authentication progress into onboarding +3/-2

Wire optional authentication progress into onboarding

• Uses a caller-provided progress sink when present and otherwise preserves the existing setup progress behavior.

src/Capacitor.Cli/Commands/SetupFacadeFactory.cs

Refactor (1) +19 / -6
TenantDiscovery.csExpose GitHub workspace listing independently of selection +19/-6

Expose GitHub workspace listing independently of selection

• Adds a list-only discovery seam where zero tenants is a valid result. Centralizes discovery error descriptions for both listing and interactive selection flows.

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

Tests (2) +74 / -1
FakeFacadeFactory.csKeep the fake facade factory compatible +2/-1

Keep the fake facade factory compatible

• Updates the test factory signature to accept the optional authentication progress dependency.

test/Capacitor.Cli.Tests.Unit/Commands/FakeFacadeFactory.cs

SetupDiscoverJsonTests.csTest discovery payload semantics and serialization +72/-0

Test discovery payload semantics and serialization

• Covers snake_case fields, nullable GitHub App metadata, provider-derived creation eligibility, empty workspace lists, and failure report defaults.

test/Capacitor.Cli.Tests.Unit/Commands/SetupDiscoverJsonTests.cs

Documentation (3) +32 / -0
README.mdDocument the discovery-only setup command +2/-0

Document the discovery-only setup command

• Adds 'kcap setup --discover' and its JSON variant to the primary setup examples, emphasizing that the command reports workspaces without configuring anything.

README.md

CHANGES.mdRecord discovery-only design decisions and tradeoffs +22/-0

Record discovery-only design decisions and tradeoffs

• Explains why discovery bypasses the normal choosing flow, how 'can_create' differs by provider, and why JSON authentication narration uses stderr.

docs/CHANGES.md

help-setup.txtDescribe discovery-only setup usage and constraints +8/-0

Describe discovery-only setup usage and constraints

• Documents non-mutating behavior, repeated sign-in, JSON stderr narration, and incompatibility with an explicit server argument.

src/Capacitor.Cli.Core/Resources/help-setup.txt

@qodo-code-review

qodo-code-review Bot commented Sep 18, 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. Single workspace discovery writes state ✓ Resolved 🔗 Cross-repo conflict ≡ Correctness
Description
RunDiscoverOnlyAsync uses the normal auto-selecting discovery flow, accepts
AuthResult.Committed, and renders only tenants observed by ReportingTenantPicker, even though
singleton tenants bypass the picker and zero or declined selections are translated into failure.
With one WorkOS membership it can publish profile, activation, or token state before reporting no
workspace, while multiple or zero memberships exit with status 1 before rendering the tenant list or
can_create flag, including the supported hosted cold-install case.
Code

src/Capacitor.Cli/Commands/SetupCommand.cs[R1747-1748]

+        var result = await facades.Create(provisioner: null, picker, requested: null)
+            .DiscoverAsync(chosen, device, CancellationToken.None);
Relevance

●●● Strong

Discovery precedents require distinguishing empty successful discovery from failed or
state-committing authentication paths.

PR-#216
PR-#501

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The cited command invokes normal discovery, renders only after accepting cancellation or commit, and
relies on the reporting picker's captured tenants. Both provider implementations bypass that picker
for a singleton tenant, after which the onboarding façade publishes or commits configuration and
tokens; multiple tenants turn the picker's null response into failure, while WorkOS maps an empty
membership list without a provisioner to NoTenantsFound, preventing the intended report. The
AuthResult contract identifies Committed as completion of the publication boundary, and the
deployment enables membership-based WorkOS discovery alongside a separate public tenant-provisioning
service, showing that an account with no existing tenant is a supported hosted state rather than
necessarily an authentication error.

Rule 2750487: Enforce ordered and exhaustive auth commit handling
src/Capacitor.Cli/Commands/SetupCommand.cs[1741-1758]
src/Capacitor.Cli/Commands/SetupDiscoverJson.cs[50-64]
src/Capacitor.Cli.Core/Auth/WorkOSDiscovery.cs[84-100]
src/Capacitor.Cli.Core/Auth/WorkOSDiscovery.cs[204-239]
src/Capacitor.Cli.Core/Auth/WorkOSDiscovery.cs[88-102]
src/Capacitor.Cli.Core/Auth/WorkOSDiscovery.cs[127-132]
src/Capacitor.Cli.Core/Auth/TenantDiscovery.cs[54-71]
src/Capacitor.Cli.Core/Auth/OnboardingFacade.cs[325-370]
src/Capacitor.Cli/Commands/SetupDiscoverJson.cs[50-63]
src/Capacitor.Cli/Commands/SetupCommand.cs[1750-1758]
src/Capacitor.Cli/Commands/SetupCommand.cs[1750-1755]
src/Capacitor.Cli.Core/Auth/AuthResult.cs[18-30]
src/Capacitor.Cli/Commands/SetupCommand.cs[1747-1758]
src/Capacitor.Cli.Core/Auth/WorkOSDiscovery.cs[84-87]
src/Capacitor.Cli.Core/Auth/WorkOSDiscovery.cs[108-123]
src/Capacitor.Cli.Core/Auth/OnboardingFacade.cs[323-332]
External repo: kurrent-io/kcap-deployments, charts/auth-proxy/values.yaml [36-47]
External repo: kurrent-io/kcap-deployments, charts/tenant-provisioner/values.yaml [14-29]
External repo: kurrent-io/kcap-deployments, charts/tenant-provisioner/templates/ingress.yaml [1-25]

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 discover-only command uses the normal onboarding discovery flow, which can auto-select and commit a singleton tenant before the reporting picker observes it, converts multi-tenant declines and empty results into failures, and therefore fails to provide a reliably read-only report for one, many, or zero tenants.

## Fix Focus Areas
- src/Capacitor.Cli/Commands/SetupCommand.cs[1741-1758]
- src/Capacitor.Cli/Commands/SetupDiscoverJson.cs[50-64]
- src/Capacitor.Cli.Core/Auth/OnboardingFacade.cs[323-370]
- src/Capacitor.Cli.Core/Auth/WorkOSDiscovery.cs[84-132]
- src/Capacitor.Cli.Core/Auth/TenantDiscovery.cs[54-71]

## Recommended Fix
Introduce a dedicated read-only discovery operation or observer that receives and returns every discovered tenant row before singleton auto-selection. Have `RunDiscoverOnlyAsync` render those rows without switching organizations, publishing or activating profiles, provisioning tenants, or storing tokens; represent zero tenants as a successful empty result, render `workspaces: []` with an accurately derived creation capability, and reserve nonzero status for actual authentication or discovery errors. Handle authentication outcomes distinctly: do not accept `AuthResult.Committed` as the expected discover-only outcome, and treat it as an invariant failure that returns nonzero without rendering an unchanged-success message.

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


2. Machine output is not valid JSON ✓ Resolved 🐞 Bug ≡ Correctness
Description
RunDiscoverOnlyAsync uses the existing setup façade while asserting that authentication progress
goes to stderr, but its ConsoleAuthProgress writes notices, browser instructions, device codes,
and polling characters to stdout. Browser and device sign-ins therefore place human-readable text
before the serialized document whenever --json is requested.
Code

src/Capacitor.Cli/Commands/SetupCommand.cs[R1745-1748]

+        // Progress narrates the sign-in on stderr, so a --json run still shows the user the URL and
+        // code they have to approve without putting a word on stdout.
+        var result = await facades.Create(provisioner: null, picker, requested: null)
+            .DiscoverAsync(chosen, device, CancellationToken.None);
Relevance

●●● Strong

Recent precedents accept protecting machine-readable stdout from diagnostic or progress output
contamination.

PR-#677
PR-#414

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The factory injects SetupCommand.StepProgress, which delegates to ConsoleAuthProgress; all
informational authentication methods in that implementation use Console.Out or Console.Write,
while the new JSON document is later written to the same stream.

src/Capacitor.Cli/Commands/SetupFacadeFactory.cs[10-17]
src/Capacitor.Cli.Core/Auth/AuthProgress.cs[43-62]
src/Capacitor.Cli/Commands/SetupCommand.cs[1745-1763]

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

## Issue description
Authentication progress from the façade is emitted on stdout, corrupting the discover command's machine-readable JSON stream.

## Fix Focus Areas
- src/Capacitor.Cli/Commands/SetupCommand.cs[1745-1763]
- src/Capacitor.Cli/Commands/SetupFacadeFactory.cs[10-17]
- src/Capacitor.Cli.Core/Auth/AuthProgress.cs[43-62]

## Recommended Fix
Supply the discover-only flow with an `IAuthProgress` implementation that writes every progress event to stderr when JSON output is requested. Keep stdout exclusively for the single serialized payload and add a command-level test that captures both streams during authentication progress.

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



Remediation recommended

3. WorkOS failures blame GitHub ✓ Resolved 🐞 Bug ≡ Correctness ⭐ New
Description
ListWorkOSAsync passes DiscoveryError.TokenRejected to TenantDiscovery.Describe, whose message
explicitly says GitHub rejected the token. A rejected WorkOS token therefore gives users the wrong
provider and misleading sign-in diagnosis.
Code

src/Capacitor.Cli.Core/Auth/OnboardingFacade.cs[R324-325]

+        if (result.Error != DiscoveryError.None)
+            return DiscoveryReport.Failure(AuthProvider.WorkOS, TenantDiscovery.Describe(result.Error));
Relevance

●●● Strong

Provider-specific authentication diagnostics are expected to identify the correct provider rather
than reuse misleading shared text.

PR-#175
PR-#387

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new WorkOS path sends every discovery error through TenantDiscovery.Describe, while that
formatter hard-codes GitHub for TokenRejected. The established WorkOS discovery flow already maps
the same error to the provider-correct message, proving the shared formatter is inappropriate for
this call site.

src/Capacitor.Cli.Core/Auth/OnboardingFacade.cs[322-325]
src/Capacitor.Cli.Core/Auth/TenantDiscovery.cs[53-58]
src/Capacitor.Cli.Core/Auth/AuthProxyClient.cs[66-81]
src/Capacitor.Cli.Core/Auth/WorkOSDiscovery.cs[74-85]

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

## Issue description
WorkOS discovery uses a shared error formatter whose token-rejection message names GitHub, so rejected WorkOS credentials produce misleading guidance.

## Fix Focus Areas
- src/Capacitor.Cli.Core/Auth/OnboardingFacade.cs[322-325]
- src/Capacitor.Cli.Core/Auth/TenantDiscovery.cs[53-58]

## Recommended Fix
Make discovery-error formatting provider-aware, or retain separate WorkOS and GitHub token-rejection messages. Ensure the WorkOS path says WorkOS rejected the token while the GitHub path retains its existing wording.

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


4. Users are told an invalid pairing works ✓ Resolved 📘 Rule violation ⚙ Maintainability ⭐ New
Description
README.md adds kcap setup --discover [--json] to Initial setup but leaves the same section
saying --discover forces discovery even when a server is configured. Supplying it with
--server-url instead reaches RunDiscoverOnlyAsync's guard and exits with status 1, so the
documented combination cannot run.
Code

README.md[339]

+kcap setup --discover [--json]               # report workspaces only, configure nothing
Relevance

●●● Strong

Documentation conflicts directly with the implemented guard; correcting user-facing CLI guidance is
a deterministic compliance fix.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Compliance rule 2270057 requires README documentation for user-facing CLI changes to match current
behavior. The added discovery example is followed by guidance that conflicts with the command guard,
which rejects the documented server combination.

Rule 2270057: Keep CLI documentation in README.md in sync with user-facing CLI changes
README.md[339-343]
src/Capacitor.Cli/Commands/SetupCommand.cs[1732-1737]

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 Initial setup documentation says `--discover` forces discovery when a server is configured, but the command rejects its use with `--server-url`.

## Fix Focus Areas
- README.md[339-343]

## Recommended Fix
Revise the paragraph after the setup examples to explain that `--discover` cannot be combined with a server argument and only reports accessible workspaces without changing configuration.

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


5. GitHub users are told to use no slug ✓ Resolved 🐞 Bug ≡ Correctness ⭐ New
Description
RunDiscoverOnlyAsync always instructs users to run kcap setup <slug> after listing workspaces.
GitHub-App rows deliberately have a null slug and are identified only by their origin URL, so that
provider's users are not given a command matching the identifier reported to them.
Code

src/Capacitor.Cli/Commands/SetupCommand.cs[1778]

+        AnsiConsole.MarkupLine("  [dim]Nothing was changed. Run `kcap setup <slug>` to use one.[/]");
Relevance

●●● Strong

Recent history accepts actionable CLI guidance corrections when retry or setup commands do not match
actual identifiers.

PR-#667
PR-#815

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new workspace model documents that GitHub-App rows have no slug, and the human renderer
consequently falls back to displaying their URL. The unconditional final line nevertheless names
only a slug, while setup's positional parser supports a full URL as the correct alternative.

src/Capacitor.Cli/Commands/SetupDiscoverJson.cs[7-12]
src/Capacitor.Cli/Commands/SetupCommand.cs[431-436]
src/Capacitor.Cli/Commands/SetupCommand.cs[1774-1778]
src/Capacitor.Cli.Core/Auth/AuthModels.cs[189-209]

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 human-readable completion message requires a slug even though GitHub-App discovery rows intentionally expose only an origin URL.

## Fix Focus Areas
- src/Capacitor.Cli/Commands/SetupCommand.cs[1774-1778]
- src/Capacitor.Cli/Commands/SetupDiscoverJson.cs[7-12]

## Recommended Fix
Render guidance that accepts the displayed identifier, such as `kcap setup <slug-or-url>`, or choose provider-specific wording that tells GitHub-App users to pass the reported URL.

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


View medium (6)
6. Cancelled discovery reports an outage ✓ Resolved 🐞 Bug ☼ Reliability ⭐ New
Description
DiscoverOnlyCoreAsync converts a null proxy configuration directly into an unreachable failure
without checking whether the supplied cancellation token caused it. Because the proxy client maps
OperationCanceledException to null or ProxyUnreachable, cancellation during either proxy request
returns an outage report instead of preserving cancellation.
Code

src/Capacitor.Cli.Core/Auth/OnboardingFacade.cs[R297-298]

+        if (proxyConfig is null)
+            return DiscoveryReport.Failure(provider, "Cannot reach the Kurrent auth service.", AuthFailureReason.Unreachable);
Relevance

●●● Strong

Recent history accepts findings where cancellation or transport failures are collapsed into
misleading availability failures.

PR-#676
PR-#875

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
AuthProxyClient catches cancellation and returns the same values used for transport outages. The
established onboarding path routes those values through Fail or Stop, which check the token and
return AuthResult.Cancelled, but the new report path constructs failures directly and bypasses
that safeguard.

src/Capacitor.Cli.Core/Auth/AuthProxyClient.cs[42-57]
src/Capacitor.Cli.Core/Auth/AuthProxyClient.cs[66-81]
src/Capacitor.Cli.Core/Auth/OnboardingFacade.cs[294-305]
src/Capacitor.Cli.Core/Auth/OnboardingFacade.cs[429-437]

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 discover-only façade translates proxy-client cancellation into ordinary unreachable failures, violating the cancellation behavior used by the existing onboarding flow.

## Fix Focus Areas
- src/Capacitor.Cli.Core/Auth/OnboardingFacade.cs[294-305]
- src/Capacitor.Cli.Core/Auth/OnboardingFacade.cs[322-325]
- src/Capacitor.Cli.Core/Auth/OnboardingFacade.cs[343-349]

## Recommended Fix
Check `ct.IsCancellationRequested` after proxy operations that collapse cancellation into failure results and preserve cancellation, preferably by throwing `OperationCanceledException` for this report-returning API. Keep genuine transport failures mapped to `DiscoveryReport.Failure`.

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


7. GitHub users get a false create flag ✓ Resolved 🐞 Bug ≡ Correctness
Description
SetupDiscoverRender.Payload sets CanCreate solely from an empty tenant count and ignores the
selected provider. An empty GitHub-App result therefore reports can_create: true even though
workspace creation is unavailable on that route and setup rejects creation flags combined with
--github.
Code

src/Capacitor.Cli/Commands/SetupDiscoverJson.cs[R35-36]

+            CanCreate: tenants.Count == 0,
+            provider);
Relevance

●●● Strong

Discovery precedents accept provider-aware handling of empty results and capability-specific
creation guidance.

PR-#501
PR-#216

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The payload ignores its provider when deriving CanCreate, while setup's existing validation
explicitly rejects --org and --slug for the GitHub provider.

src/Capacitor.Cli/Commands/SetupDiscoverJson.cs[28-36]
src/Capacitor.Cli/Commands/SetupCommand.cs[1696-1705]

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 machine-readable creation capability is inferred only from tenant count, so the GitHub-App route advertises unsupported workspace creation.

## Fix Focus Areas
- src/Capacitor.Cli/Commands/SetupDiscoverJson.cs[28-36]
- src/Capacitor.Cli/Commands/SetupCommand.cs[1696-1705]

## Recommended Fix
Set `CanCreate` only when the selected provider supports workspace provisioning and the account has no existing workspace. Add an empty GitHub discovery payload test asserting that the flag is false.

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


8. A named workspace can be ignored ✓ Resolved 🐞 Bug ≡ Correctness
Description
RunDiscoverOnlyAsync tests only args[1] for a positional workspace even though --discover
itself commonly occupies that slot. Commands such as kcap setup --discover acme consequently sign
in and discover instead of refusing the explicitly named workspace as documented.
Code

src/Capacitor.Cli/Commands/SetupCommand.cs[R1734-1735]

+        if (GetArg(args, "--server-url") is not null || (args.Length > 1 && !args[1].StartsWith('-'))) {
+            await Console.Error.WriteLineAsync("--discover finds the workspaces you belong to; drop the server argument to use it.");
Relevance

●●● Strong

Accepted CLI validation precedents support rejecting malformed positional arguments instead of
silently ignoring them.

PR-#383
PR-#536

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The command receives setup at index zero, while the new condition inspects only index one; placing
--discover there means any following positional tenant escapes validation.

src/Capacitor.Cli/Commands/SetupCommand.cs[428-442]
src/Capacitor.Cli/Commands/SetupCommand.cs[1731-1738]
test/Capacitor.Cli.Tests.Unit/Commands/SetupCommandTests.cs[1284-1290]

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

## Issue description
Discovery's conflict check only recognizes a positional workspace in the first argument slot after `setup`, allowing later positional values to be silently ignored.

## Fix Focus Areas
- src/Capacitor.Cli/Commands/SetupCommand.cs[1731-1743]
- src/Capacitor.Cli/Commands/SetupCommand.cs[431-442]

## Recommended Fix
Parse the full discovery argument sequence against an explicit set of accepted flags and their values. Reject any positional workspace regardless of ordering, as well as malformed or conflicting server arguments, before starting authentication.

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


9. GitHub users receive a rejected command ✓ Resolved 🐞 Bug ≡ Correctness
Description
The empty-result branch always recommends kcap setup --org ... --slug ... without considering the
provider used for discovery. After GitHub-App discovery, following that guidance with the same
provider is rejected by setup, while the existing GitHub no-tenant path instead directs users to an
administrator or a server URL.
Code

src/Capacitor.Cli/Commands/SetupCommand.cs[R1766-1768]

+        if (payload.Workspaces.Count == 0) {
+            AnsiConsole.MarkupLine("  No Capacitor workspace found for this account.");
+            AnsiConsole.MarkupLine("  [dim]Create one with `kcap setup --org \"<name>\" --slug <slug>`.[/]");
Relevance

●●● Strong

A close discovery precedent accepts provider-specific handling when generic empty-result guidance is
misleading.

PR-#501
PR-#216

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new empty branch emits one creation command for every provider, but setup rejects that creation
mode with GitHub and the existing GitHub discovery error supplies different actionable guidance.

src/Capacitor.Cli/Commands/SetupCommand.cs[1696-1705]
src/Capacitor.Cli/Commands/SetupCommand.cs[1766-1770]
src/Capacitor.Cli.Core/Auth/TenantDiscovery.cs[54-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 human-readable empty result recommends hosted workspace creation even when discovery used the GitHub-App provider, which cannot perform that operation.

## Fix Focus Areas
- src/Capacitor.Cli/Commands/SetupCommand.cs[1766-1770]
- src/Capacitor.Cli.Core/Auth/TenantDiscovery.cs[54-58]

## Recommended Fix
Branch the empty-state guidance on the selected provider. Preserve the creation command for providers that support it, and for GitHub direct users to install the app through an administrator or supply a self-hosted server URL.

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


10. Discovery types break file ownership ⊘ Outdated 📘 Rule violation ⚙ Maintainability
Description
SetupDiscoverJson.cs declares five top-level types, including three public types, while its
filename identifies only SetupDiscoverJson. The workspace record, serializer context, renderer,
and picker therefore lack their own file ownership and do not fit any permitted companion-type
exception.
Code

src/Capacitor.Cli/Commands/SetupDiscoverJson.cs[12]

+public sealed record DiscoveredWorkspaceJson(string? Slug, string Url, string? Name, string Provider);
Evidence
Rule 3162234 permits one primary top-level type per file except for narrowly defined companion
patterns. The new file contains records, a serializer context, a renderer, and a picker rather than
an enum extension, hierarchy, or descriptor registry.

Rule 3162234: One primary type per file, with only narrow documented exceptions
src/Capacitor.Cli/Commands/SetupDiscoverJson.cs[7-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 new source file contains several unrelated top-level public and internal types despite the one-primary-type-per-file convention.

## Fix Focus Areas
- src/Capacitor.Cli/Commands/SetupDiscoverJson.cs[7-50]

## Recommended Fix
Move `DiscoveredWorkspaceJson`, `SetupDiscoverJsonContext`, `SetupDiscoverRender`, and `ReportingTenantPicker` into files named after their respective types, leaving `SetupDiscoverJson.cs` with its matching primary record.

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


11. Provider values disagree in one result ⊘ Outdated 🐞 Bug ≡ Correctness
Description
SetupDiscoverRender.Payload copies each tenant's upstream Provider verbatim while assigning the
canonical selected provider to the top-level field. WorkOS responses preserve the proxy's WorkOS
casing, so one document can expose WorkOS on rows and workos at the top level despite both
identifying the same provider.
Code

src/Capacitor.Cli/Commands/SetupDiscoverJson.cs[R30-36]

+            [.. tenants.Select(t => new DiscoveredWorkspaceJson(
+                t.Slug,
+                t.Origin,
+                string.IsNullOrWhiteSpace(t.DisplayName) ? null : t.DisplayName,
+                t.Provider))],
+            CanCreate: tenants.Count == 0,
+            provider);
Relevance

●● Moderate

Provider normalization is plausible, but no close precedent establishes casing normalization for
discovery payload rows.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The model documents that the WorkOS proxy emits WorkOS, while the canonical constant is workos;
the new renderer uses those two independently sourced values without normalization.

src/Capacitor.Cli.Core/Auth/AuthModels.cs[197-205]
src/Capacitor.Cli/Commands/SetupDiscoverJson.cs[28-36]
src/Capacitor.Cli.Core/Auth/OAuthLoginFlow.cs[13-17]

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

## Issue description
Workspace rows retain upstream provider casing while the enclosing payload uses the canonical provider identifier, producing inconsistent machine-readable values.

## Fix Focus Areas
- src/Capacitor.Cli/Commands/SetupDiscoverJson.cs[28-36]
- src/Capacitor.Cli.Core/Auth/AuthModels.cs[197-205]

## Recommended Fix
Map each row's provider to the canonical `AuthProvider` value before serialization, using the same normalization as provider selection. Extend the WorkOS rendering test with an upstream-cased provider and assert lowercase values at both levels.

ⓘ 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-deployments (sha: e2139f7d)
Review mode: ⚖️ Balanced: Downgraded extended -> standard: change is below the extended eligibility bar (hunks 15/18, lines 366/200; both must reach the floor). Router rationale: This adds authentication-driven discovery, CLI validation/output modes, provider-specific behavior, and new facade/reporting paths across multiple files, with several previously identified independent defects indicating high bug density.

Grey Divider

Tip of the day
💡 Did you know, you can type 'qodo, fix this' on a finding and the fix lands right on your PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread src/Capacitor.Cli/Commands/SetupDiscoverJson.cs Outdated
Comment thread src/Capacitor.Cli/Commands/SetupCommand.cs Outdated
Comment thread src/Capacitor.Cli/Commands/SetupCommand.cs Outdated
Comment thread src/Capacitor.Cli/Commands/SetupDiscoverJson.cs Outdated
Comment thread src/Capacitor.Cli/Commands/SetupDiscoverJson.cs Outdated
Comment thread src/Capacitor.Cli/Commands/SetupCommand.cs Outdated
Comment thread src/Capacitor.Cli/Commands/SetupCommand.cs Outdated
@George-Payne
George-Payne marked this pull request as draft September 18, 2026 08:50
The first cut asked the normal discovery flow for the rows and supplied a
picker that declined, on the reasoning that a decline is pre-boundary and
therefore publishes nothing. It publishes nothing only when the picker is
asked. A sole workspace is auto-selected before any picker is consulted, so
that route configured the machine for exactly the case a report is most
needed and then reported no workspaces; an account with none failed instead
of answering.

`DiscoverOnlyAsync` goes at the rows directly: sign in, list through the
proxy, return. Publishing is a separate step this route never reaches, and no
provisioner is passed, so nothing is written and nothing is created.

`can_create` is now the lane's answer rather than a count of an empty list.
Only the hosted lane provisions; a GitHub-App account gets a workspace by
having the app installed on an org, so offering it creation is a dead end,
and the human hint for that lane says so.

The progress sink takes the stream to write to. Its notices, browser lines,
device code and poll ticks all went to stdout, which would have put human
text in front of the document -- the sign-in still narrates itself under
--json, on stderr.
@George-Payne
George-Payne marked this pull request as ready for review September 18, 2026 08:59
…etup-discover

# Conflicts:
#	docs/CHANGES.md
Comment thread README.md
Comment thread src/Capacitor.Cli/Commands/SetupCommand.cs
@qodo-code-review

Copy link
Copy Markdown

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

The guard read args[1] only, which is where the tenant argument normally
goes - but --discover commonly occupies that slot, and anything after it
escaped. `setup --discover acme` signed in and discovered instead of
refusing the workspace it had been handed.

The check now scans every bare token after the verb, less the values of the
flags that take one, so --default-visibility private is not read as a
workspace called private. Naming one through --server-url, --org or --slug
counts the same way.
Four things the rework got wrong, all of them a message to a person.

A rejected token borrowed GitHub's wording on the WorkOS lane, which sends
that user to the wrong sign-in; the error now names the lane it came from,
and the unreachable-service message names neither, because it is not either
provider's doing.

A GitHub-App row is identified by origin and carries no slug, so closing with
`setup <slug>` handed those users a command they cannot run. The hint now
offers the URL form when no row has a slug.

A cancelled request comes back from the proxy looking like an unreachable
one, so cancelling discovery reported an outage and pointed the reader at a
service that is fine.

And the README described a --discover that setup never had - the flag was
documented and unimplemented. It exists now, so the sentence says what it
does.
if (GetArg(args, "--slug") is not null) return true;

string[] valued = ["--server-url", "--org", "--slug", "--default-visibility", "--daemon-name", "--profile"];
var values = valued.Select(flag => GetArg(args, flag)).Where(v => v is not null).ToHashSet();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The values are excluded by text, not by their argument positions. For example, kcap setup --discover --profile acme acme treats both acme tokens as the --profile value, so the extra positional workspace is silently accepted and discovery starts instead of rejecting the invalid pairing. Skip each valued flag and only its following token while scanning, and cover a positional token that equals a flag value.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 167dfa4, by removing the value list rather than making it positional. --discover now takes a closed set (--json, --github, --device, --no-prompt, plus the process-wide --no-update-check), and the scan refuses the first argument outside it, so there is no set of values for a positional to hide in.

Your example is refused at --profile and discovery never starts. That is pinned by A_workspace_repeating_an_earlier_value_does_not_start_discovery (a245c52), and the mirrored order, --discover acme --daemon-name acme, by A_workspace_is_still_caught_when_a_later_value_repeats_it.

Closed set rather than skipping each valued flag's following token, because a skip list has to know every flag that takes a value. The one here already missed --plugin-scope and --use-provider-api-key, so --discover --plugin-scope user was refused as a workspace called "user". The cost: options --discover used to ignore, such as --daemon-name and --default-visibility, are now refused by name.

Run against the published AOT binary: kcap setup --discover acme, kcap setup acme --discover --json and kcap setup --discover --server-url <url> each exit 1 with the workspace refusal, and kcap setup --discover --plugin-scope user exits 1 naming the flag, all before any sign-in.

@realtonyyoung realtonyyoung left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One argument-validation issue remains in --discover: a positional workspace can be missed when it matches another option’s value. I left a focused inline comment. This was a source review only; I did not build or run tests.

alexeyzimarev and others added 3 commits September 18, 2026 15:10
The scripts answer the org switch and the token exchange, and a control runs the same one through the choosing route, so the tests fail if this route ever reaches either. A cancel thrown by the sign-in escaped the facade while its siblings return one; it now comes back as a cancelled report.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A list of what to refuse has to know every flag that takes a value, or it reads --plugin-scope user as a workspace called user. kcap login --discover keeps its own meaning, so each help text now points at the other.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@alexeyzimarev alexeyzimarev changed the title [AI-2935] Report the workspaces discovery finds, without choosing one Report the workspaces discovery finds, without choosing one Sep 18, 2026
alexeyzimarev and others added 2 commits September 18, 2026 15:12
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…etup-discover

# Conflicts:
#	docs/CHANGES.md
@alexeyzimarev
alexeyzimarev merged commit f317668 into main Sep 18, 2026
8 checks passed
@alexeyzimarev
alexeyzimarev deleted the georgepayne/ai-2935-setup-discover branch September 18, 2026 13:31
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.

3 participants