Skip to content

feat: experiment run does what the run-experiment action does - #80

Merged
achoimet merged 3 commits into
mainfrom
feat/run-expectations
Sep 29, 2026
Merged

achoimet merged 3 commits into
mainfrom
feat/run-expectations

Conversation

@achoimet

Copy link
Copy Markdown
Member

Step 1 of moving steadybit/run-experiment onto the CLI: experiment run gains what the action offers. Step 2 turns the action into a thin wrapper that installs the CLI and maps its inputs and outputs, with the same inputs and outputs, so customers change nothing.

Parity, input by input

run-experiment CLI
experimentKey -k
externalId new: --external-id without --template. It fails if no experiment, or more than one, has that external id.
parallel --allowParallel
maxRetries (another running, 30 s apart) new: --busy-retries / --busy-retry-interval (default 30 s). They take priority over --yes, so the run is never started in parallel instead. They also cover the run the platform accepts and then cancels "because another experiment was running".
expectedState / expectedReason new: --expect-state / --expect-reason. They pass once the run reaches the state, as the action does (RUNNING passes before the end), and fail when the run ends in another state. The reason must match exactly.
maxRetriesOnExpectationFailure / delayBetweenRetriesOnExpectationFailure new: --expectation-retries / --expectation-retry-interval. They run the experiment again.
maxRetriesOnValidationFailure / delayBetweenRetriesOnValidationFailure --retries / --retryInterval. Now the last attempt is kept on the platform, as the action does. Before, no attempt was kept.
outputs executionId, executionState, executionReason, executionUrl --report run.json: id, state, reason, and new apiLocation, the run's Location, which is what the action outputs as executionUrl. The reason falls back to the legacy failureReason, as the action reads it.

Without the new flags, nothing changes: a run still has to complete, with the same messages.

Also fixed: the platform answers both "another experiment running" and validation errors with 422. With --retries, the first used to be retried as a validation error. The problem type is now checked first.

Found on dev: the platform's "another experiment running" rule spans teams. A run of team CLI was cancelled because an ADM run was going. So --busy-retries matters in a shared tenant. It's also why the weekly platform test only checks that a retry happens, not that the platform eventually becomes free.

Testing

  • Unit tests:

    • expected states (FAILED passes; RUNNING passes early, and polling stops there; another end fails, naming both);
    • reason mismatch, and the unchanged default;
    • busy retries, both succeeding and exhausted, never with allowParallel=true;
    • a run cancelled for another experiment, retried;
    • expectation retries, with the report holding the last run and its apiLocation;
    • external id found, none, two, and with -k;
    • bad state, and expectations with --no-wait;
    • keeping only the last validation attempt.

    go test -race ./... and go vet (also GOOS=windows) pass.

  • Live on dev, team CLI, wait-only experiments, all deleted:

    • --external-id … --expect-state RUNNING passed after 6 s, and the report had RUNNING and apiLocation;
    • --expect-state FAILED on a completing run failed with "completed, but failed was expected";
    • --busy-retries 4 waited through two refusals while another run went, then completed, with --yes.
  • e2e/platform.sh now covers the external id, the expected state and busy retries. All 14 checks pass locally against dev.

The steadybit/run-experiment GitHub Action has its own API client; to run it on
the CLI instead, `experiment run` needs what it offers:

- --expect-state passes once the run reaches a state, which need not be its
  end (RUNNING), and fails when it ends in another; --expect-reason also
  requires the reason. Without them a run has to complete, as before.
- --expectation-retries runs the experiment again when a run did not end as
  expected, --expectation-retry-interval apart.
- --busy-retries waits and tries again while another experiment runs, both
  when the platform refuses the run and when it cancels it right after
  accepting it, and never starts it in parallel instead, which --yes would do.
  The platform's rule spans teams, so this matters in a shared tenant.
- --external-id without --template runs the experiment with that external id.
- The JSON report gives each run's apiLocation, the action's executionUrl.
- With --retries, the last attempt is kept on the platform, as the action does,
  so a run shows what was wrong.

"Another experiment running" is now told apart before validation errors: the
platform answers both with 422, so with --retries the former used to be
retried as a validation error. Both run paths share one function that starts,
waits and retries. The weekly platform test covers the new flags.
A change to the platform test can use flags the latest release does not have
yet, so on a pull request the CLI is built from the branch.
The expected-state check threw the CLI's output away, so a failure in CI said
nothing about why. It now runs through exits_with, which prints it, and the
check on a run ending otherwise asserts the message, not only the exit code.
@achoimet
achoimet merged commit 6a8c0c5 into main Sep 29, 2026
13 checks passed
@achoimet
achoimet deleted the feat/run-expectations branch September 29, 2026 16:02
@github-actions github-actions Bot locked and limited conversation to collaborators Sep 29, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant