Skip to content

Surface CI secret variant shadowing in depot ci secrets list#532

Open
robstolarz wants to merge 6 commits into
mainfrom
worktree-dep-5304-secret-variants
Open

Surface CI secret variant shadowing in depot ci secrets list#532
robstolarz wants to merge 6 commits into
mainfrom
worktree-dep-5304-secret-variants

Conversation

@robstolarz

@robstolarz robstolarz commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Summary

A CI secret or variable can carry several variants, each scoped to a repository, environment, branch, or workflow, alongside a catch-all default. At runtime the most specific matching variant wins — so a repo-scoped variant quietly outranks the default for its repository.

That rule was invisible at the CLI. If someone re-imports a value into the default while a stale repo-scoped variant still exists, the import looks like it succeeded but the old repo-scoped value keeps winning, and nothing explains why the "updated" secret never takes effect. This change surfaces that resolution both when you list and at the moment you write — for both secrets and variables, which share the same variant model and the same blind spot.

What changed

Listing reveals the winner. The --repo, --env, --branch, and --workflow selectors are now sent to the server as a job context instead of a hard client-side filter. The server already knows how to resolve variants for a context: it returns the relevant variants ordered winner-first and tags each with its resolution. The CLI now asks for that and renders it, rather than filtering variants out itself — which also fixes a subtle bug where the old exact-match client filter could drop a variant the server had legitimately resolved through a pattern (for example a branch=release/* variant matching --branch release/1).

The list table gains a STATUS column whenever a context is supplied: active (wins) (this variant wins), shadowed (a more specific variant overrides it), or may win (needs branch/env) (it could still win with more context). A short legend explains the terms. Without a context there is no single winner to report, so the column is omitted and a hint points at the selector flags. list <name> takes the same path, so inspecting a single secret also shows which variant wins. The SCOPE column shows each variant's attributes in both modes.

Writing warns when the value is shadowed. After secrets set, secrets add, or secrets bulk, the CLI checks whether the variant it just wrote is actually the one that will apply. For each sibling variant's scope it asks the server — through the same context-aware list — which variant wins there, and prints a stderr advisory only when the server reports the just-written variant as lower-priority. This closes the "reported success but nothing changed" gap at the moment it bites: re-importing the default while a repo-scoped variant still wins now prints a warning pointing at the exact depot ci secrets list … --repo … that reveals the shadowing variant. All precedence stays server-authoritative; the CLI never recomputes the specificity weights. The probes are de-duplicated by scope and capped so a large bulk import can't fan out into unbounded follow-up requests.

variables gets the same treatment. variables list now forwards the same selectors as job context, gains the SCOPE and STATUS columns, and variables set / add warn on the write path — mirroring secrets and reusing the shared shadow-probe and warning helpers. One difference under the hood: variables report resolution only at the group level (there is no per-variant resolution field on the wire, as there is for secrets), so the per-row STATUS is derived from the server's group verdict plus its winner-first ordering — the first row wins, the rest are shadowed — rather than read off each variant. Precedence stays entirely server-authoritative either way.

Proto bindings. The generated v3beta2 secret and variable proto bindings had drifted behind their schema — the request context field and (for secrets) the per-variant resolution enum were defined but never generated — so the CLI had no way to send a context or read the resolution back. They are regenerated here with the pinned buf plugins.

Example

$ depot ci secrets list NPM_TOKEN --repo owner/repo
NAME              VARIANT   SCOPE                  UPDATED               STATUS
----------------  --------  ---------------------  --------------------  ------------
NPM_TOKEN         repo-a    repository=owner/repo  ...                   active (wins)
NPM_TOKEN         default   all                    ...                   shadowed

STATUS: active (wins) = wins this context · shadowed = overridden by a more specific variant · may win = could win with more context
$ printf '%s' "$NPM_TOKEN" | depot ci secrets bulk --from-stdin
Successfully added CI secret 'NPM_TOKEN' variant 'default'
Warning: the value you set for "NPM_TOKEN" is shadowed for some jobs by a more specific variant.
         Run `depot ci secrets list NPM_TOKEN --repo owner/repo` to see which variant wins.

The stale repo-scoped variant that keeps winning shows as active (wins), the freshly re-imported org-wide default shows as shadowed, and the re-import now says so out loud.

Notes

The server side (context resolution and winner-first ordering) already shipped; this is the CLI catching up to surface it. No server change is required.

Deferred / known-open

  • Full per-variant resolution parity for variables. Variables expose resolution only at the group level on the wire, so per-row STATUS is derived from the winner-first ordering rather than a per-variant field. Exposing VariableVariant.resolution (as secrets already do) would let variables use the exact same per-row path; it needs a server + proto change and is left as a follow-up.
  • Write-time warning is precise, not exhaustive. It probes each sibling scope (widened with the written variant's own dimensions) and warns on a definite server lower-priority; a few multi-dimension cross-shadows can still resolve to indeterminate and not warn. list remains the complete tool for inspecting resolution, and the warning always points there.

Ref: DEP-5304


Note

Medium Risk
Changes how CI secret/variable list and write UX interpret scoped variants and adds extra list RPCs after writes; precedence remains server-side but mis-synced proto/server behavior could confuse resolution display.

Overview
CI secrets and variables can have multiple scoped variants where the most specific match wins at runtime; the CLI previously filtered lists client-side and gave no signal when a write was overridden by a more specific variant.

Listing now sends --repo / --env / --branch / --workflow as server job context (not attributes filters), maps per-variant resolution into JSON and a STATUS table column (active, shadowed, may win), and drops client-side variant filtering so server resolution and pattern matching stay authoritative. The same flow applies to secrets list and variables list (variables derive per-row status from group resolution + winner-first ordering).

Writes (set / add / bulk) run bounded follow-up context-aware list probes after success and print a stderr shadow warning with a suggested list command when the server marks the written variant as lower-priority.

API/proto: CIListSecretVariants / CIListVariableVariants use Context on list RPCs; generated v3beta2 bindings add context, variant/group resolution, and ListSecretAttributes / ListVariableAttributes stubs.

Reviewed by Cursor Bugbot for commit 21a4447. Bugbot is set up for automated code reviews on this repo. Configure here.

The generated Go bindings for the CI secret service had drifted behind their
.proto source: the ListSecretsRequest.context field and the per-variant
VariantResolution enum were defined in the schema but missing from the
generated code, so the CLI had no way to ask the server to resolve variants
or to read back which one wins.

Regenerate with the pinned buf plugins so the bindings match the schema.

DEP-5304

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@linear-code

linear-code Bot commented Jul 8, 2026

Copy link
Copy Markdown

DEP-5304

Comment thread pkg/cmd/ci/secrets.go
Comment thread pkg/cmd/ci/secrets.go

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 2fab160. Configure here.

Comment thread pkg/cmd/ci/variants.go
Comment thread pkg/cmd/ci/variants.go
robstolarz and others added 5 commits July 10, 2026 14:41
A CI secret can have several variants scoped by repository, environment,
branch, or workflow, plus a catch-all default. At runtime the most specific
matching variant wins. That means a repo-scoped variant silently overrides
the default for its repository, and `secrets list` gave no hint this was
happening: re-importing a value into the default appeared to succeed while
the stale repo-scoped variant kept winning.

Route `secrets list` through the context-aware ListSecrets path. The
--repo/--env/--branch/--workflow selectors are now sent as a job context
rather than a hard filter, so the server annotates every variant with its
resolution and orders winners first instead of dropping variants. The table
gains a STATUS column that labels each variant active, shadowed, or
candidate for the given context, and a legend explains the terms. Without a
context there is no single winner to report, so the column is omitted and a
hint points at the selector flags. `list <name>` takes the same path so it,
too, shows which variant wins.

DEP-5304

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…bels

Adds a write-path advisory: after `secrets set/add/bulk`, probe each sibling
variant scope through the context-aware list RPC and warn on stderr when the
server marks the just-written variant lower-priority for some job. Precedence
stays server-side; no specificity weights in the CLI.

Refines the list STATUS labels to active (wins) / shadowed / may win, moving
STATUS to the trailing column since its labels are the widest.

Ref: DEP-5304

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sync the vendored variables proto with canonical: add ListVariablesRequest.context
(job context the server resolves against), Variable.resolution, and the
ListVariableAttributes RPC. DEP-5304.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mirror the secrets work onto variables. `variables list` now forwards
--repo/--env/--branch/--workflow as job context so the server resolves each
group (which variant wins, which are shadowed) and orders winners first; the
table gains a SCOPE column and, when context is given, a STATUS column
(active (wins) / shadowed / may win). `variables set`/`add` warn on stderr
when the value just written is shadowed by a more specific variant.

Variables report resolution only at the group level, so per-row STATUS is
derived from the group verdict plus the server winner-first ordering rather
than a per-variant field (see variableVariantRowResolution). The shadow-probe
and warning helpers are shared with secrets. DEP-5304.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tively

Two refinements to the write-path shadow check. Bulk writes now share one probe
budget across every item, so a large dotenv import cannot fan out into hundreds
of advisory list reads after the writes already succeeded; a single set/add
still gets its own full budget. And the disjoint check now compares repository
scopes case-insensitively, matching resolution everywhere else in this file, so
a sibling scoped to owner/Repo is no longer wrongly skipped against a written
owner/repo. DEP-5304.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@robstolarz robstolarz force-pushed the worktree-dep-5304-secret-variants branch from a929289 to 21a4447 Compare July 10, 2026 21:45
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