Skip to content

fix(vars:set): allow creating action-scoped variables#21

Merged
jtsternberg merged 1 commit into
masterfrom
fix/vars-set-action-scope
Jun 4, 2026
Merged

fix(vars:set): allow creating action-scoped variables#21
jtsternberg merged 1 commit into
masterfrom
fix/vars-set-action-scope

Conversation

@jtsternberg

Copy link
Copy Markdown
Owner

Problem

It was impossible to create an action-scoped environment variable with buddy vars:set. Two validations contradicted each other:

  • buddy vars:set --pipeline=506857 --action=1558732 -- KEY VALFailed to set variable: Only one scope is allowed: project, pipeline, action, sandbox
  • buddy vars:set --action=1558732 -- KEY VAL--action requires --pipeline. An action belongs to a specific pipeline.

--action requires --pipeline, but supplying both was rejected as "only one scope" — so no invocation could ever yield an action-scoped variable. (Pipeline scope alone worked fine; only action scope was broken.)

Root cause

SetCommand::execute() built the API payload with three independent if blocks:

if ($hasPipeline) { $data['pipeline'] = ['id' => ...]; }
if ($hasAction)   { $data['action']   = ['id' => ...]; }

So --pipeline --action wrote both scope objects. The Buddy API accepts only one scope object per variable and rejected the payload.

The bug was invisible to the test suite because the existing action-scope test mocked createVariable and never asserted the payload it received.

The fix

Emit exactly one scope object via an if/elseif chain (most-specific wins: action > pipeline > project > workspace):

  • Action scope sends only action: {id}. --pipeline is used purely as routing/context for the existing-variable lookup (pipelineId + actionId filters), never as a second scope object.
  • --action still requires --pipeline (an action needs its pipeline to route the lookup), but the two no longer collide.
  • --project remains mutually exclusive with --pipeline/--action — they are genuinely different scopes.
  • Help text now disambiguates scope selectors (--project/--pipeline/--action) from routing context (--workspace, which is never a scope).

Tests

  • Strengthened the pipeline- and action-scope tests to assert the actual payload sent to createVariable, and added a project-scope payload test. The action-scope assertion fails against the old code (proper TDD red → green).
  • Full suite: 271 passed, 668 assertions.
  • Lint: pint --test clean.

Live API verification

Confirmed end-to-end against the real Buddy API (then cleaned up — the caller's existing pipeline-scoped NODE_OPTIONS was left untouched):

$ buddy vars:set --workspace=awesomemotive --pipeline=506857 --action=1558732 -- BUDDY_CLI_ACTIONSCOPE_TEST "ok"
  → created id 2109820, action.id=1558732, no pipeline scope in record
$ buddy vars:show 2109820 → Scope = action:1558732   ✅
$ buddy vars:delete 2109820 --force → deleted

Now-working command

buddy vars:set --pipeline=506857 --action=1558732 -- NODE_OPTIONS "--max-old-space-size=8192"

🤖 Generated with Claude Code

vars:set built the API payload with three independent if-blocks, so
`--pipeline=X --action=Y` emitted BOTH a `pipeline` and an `action`
scope object. Buddy's API accepts only one scope object and rejected
the request ("Only one scope is allowed: project, pipeline, action,
sandbox"). Combined with `--action` requiring `--pipeline`, there was
no accepted invocation that could ever produce an action-scoped
variable.

Emit exactly one scope object via an if/elseif chain (most-specific
wins: action > pipeline > project > workspace). For action scope, send
only the `action` reference; `--pipeline` is used solely as routing
context for the existing-variable lookup, not as a second scope.

Also disambiguate scope selectors (--project/--pipeline/--action) from
routing context (--workspace, never a scope) in the help text.

Tests now assert the actual payload shape per scope (the prior
action-scope test passed only because it mocked createVariable without
checking what was sent). Verified end-to-end against the live Buddy
API: created an action-scoped var on pipeline 506857 / action 1558732,
confirmed Scope=action:1558732, then deleted it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 4, 2026 20:06

Copilot AI 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.

Pull request overview

Fixes vars:set scope handling so action-scoped variables can be created/updated by ensuring the API payload contains exactly one scope object (action > pipeline > project > workspace), while still requiring --pipeline as routing context when --action is used.

Changes:

  • Update SetCommand to emit a single scope object via an if/elseif chain and adjust filters for existing-variable lookup.
  • Improve vars:set help text to clarify scope selectors vs --workspace routing context, including an action-scope example.
  • Strengthen/add integration tests to assert the exact payload passed to createVariable() for pipeline/action/project scopes.

Notes (blocking to keep bundled docs in sync):

  • The bundled Claude plugin contains at least one now-misleading statement about vars:set scope rules (e.g., claude-plugin/agents/cicd-specialist.md “use --project OR --pipeline” omits the valid --pipeline + --action pairing). Since this PR changes CLI behavior, the plugin should be updated in the same PR to avoid stale guidance.
  • README.md currently documents vars:set but doesn’t mention action-scoped variables; consider updating it to include the now-working --pipeline --action usage.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
src/Commands/Variables/SetCommand.php Fixes scope payload construction to allow action-scoped variables and updates help text accordingly.
tests/Integration/Commands/VariablesCommandsTest.php Adds payload-capture assertions to ensure the correct scope object is sent to the API for each scope.

Comment on lines +119 to +122
// Pipeline scope emits exactly one scope object: pipeline.
$this->assertSame(['id' => 123], $captured['pipeline'] ?? null);
$this->assertArrayNotHasKey('action', $captured);
$this->assertArrayNotHasKey('project', $captured);
Comment on lines +153 to +157
// a payload carrying both `pipeline` and `action` ("Only one scope is allowed").
// --pipeline is routing/context for resolving the action, not a second scope.
$this->assertSame(['id' => 456], $captured['action'] ?? null);
$this->assertArrayNotHasKey('pipeline', $captured);
$this->assertArrayNotHasKey('project', $captured);
Comment on lines +184 to +186
$this->assertSame(['name' => 'my-project'], $captured['project'] ?? null);
$this->assertArrayNotHasKey('pipeline', $captured);
$this->assertArrayNotHasKey('action', $captured);
@jtsternberg jtsternberg merged commit 10b698a into master Jun 4, 2026
1 check passed
@jtsternberg jtsternberg deleted the fix/vars-set-action-scope branch June 4, 2026 20:32
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