Repository navigation
Add authenticated cancel experiment API for the CLI - #712
Merged
Merged
Conversation
Add POST /api/cli/cancelExp and move cancellation into one service shared with the web UI (#710). - The backend Job deletion must be accepted before the experiment is marked CANCELLED. status and finished are set in one update that is conditional on the status still being RUNNING. - The CLI can cancel only the owner's experiments. In the web UI the owner or an admin can cancel, with the role read from the session. cancelExperimentById used to accept any caller. - CLI cancellations go through withAudit and fail closed with 503 degraded when the audit log is unavailable. - Only RUNNING experiments can be cancelled. A repeated cancel returns already_cancelled. A runner that finishes during the cancel keeps its final status. - The backend returns 404 for a missing Job, so a retry after a partial failure reconciles the status. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cancel button in the experiment list and the Stop button on the admin
page now follow CANCELLABLE_STATUSES, so a CREATED experiment no longer
shows a button that fails when clicked.
cancelExperimentById returns expected refusals as { ok: false, message }
instead of throwing: Next.js replaces the message of errors thrown by a
server action with a generic one in production, so the UI could never show
why a cancellation was refused. Both pages throw client-side and display
the message in the toast.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
rhit-changs
enabled auto-merge
November 12, 2026 23:17
rhit-changs
self-requested a review
November 12, 2026 23:18
rhit-changs
approved these changes
Nov 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
#711 was merged into main as
d1ccafe. This PR now targetsmainand precedes #714.Closes #710
Summary
Adds
POST /api/cli/cancelExpso the CLI (and agents using a CLI token) can cancel a running experiment. Web cancellation now goes through the same logic.CANCELLEDonly after the backend accepts the Job deletion.statusandfinishedare set in one conditional update (status: RUNNING→CANCELLED,finished: true), so a runner that already finished is not overwritten and the two fields can't disagree.cancelExperimentByIdauth fix (related to Security hardening: enforce server-side authorization and protect user data #708). This server action used to accept any caller. It now resolves the authenticated user and current role from the database and allows only the owner or a current admin.withAudit(actioncancel_experiment, agentcli). If the audit write fails, the route returns 503degradedand nothing is cancelled. Web cancellation is not audited./cancelExperimentvalidatesjobName(must start withrunner-) and returns 404 instead of 500 when the Job doesn't exist.Responses
{success:true, eid, outcome:"cancel_requested", status:"CANCELLED", job_termination_confirmed:false}outcome:"already_cancelled"error:"Other"error:"auth_error"error:"forbidden"error:"not_found"error:"not_cancellable", statuserror:"status_update_failed", job_deletion_requested:true. Retrying fixes it.error:"backend_error"error:"degraded"Layout
lib/experimentCancelCore.mjs: pure logic with injected deps, same pattern asagentAuditCore.mjs.lib/experimentCancelStore.ts: MongoDB and backend calls. These are kept out ofexperimentCancel.tsbecause the agent-audit static test forbids update/delete in any file that references the audit log.lib/experimentCancel.ts: entry points for the web and CLI. Not a'use server'module.app/api/cli/cancelExp/route.ts: the route.Behavior change
On the web, a
CREATEDexperiment can no longer be cancelled. It used to be markedCANCELLEDeven though the Job could still start later. The cancel button in the experiment list and the Stop button on the admin page now followCANCELLABLE_STATUSES, so they are hidden or disabled forCREATED.cancelExperimentByIdreturns expected refusals as{ ok: false, message }instead of throwing, because Next.js replaces the message of errors thrown by a server action with a generic one in production. The UI shows the message in the toast.Cancellation races
Job deletion remains asynchronous. Runner updates now include an atomic
status: { $nin: ['CANCELLED', 'ARCHIVED'] }condition so a late completion cannot overwrite an accepted cancellation or an archived terminal experiment. Experiment IDs are canonicalized before constructing the Job name, including uppercase ObjectIds. Missing-Job retries remain supported.Testing
cc @rhit-changs (author of #710)
🤖 Generated with Claude Code
Collaborator review integration
Integrated #711 at
d1faa36, including the shared submission allowlist, serialized SSE authorization/query handling, copy-error recovery, archive state validation, and shared server utilities. Preserved collaborator commit54d2183, including the cancellation UI behavior and structured Server Action refusal messages. The archive/runner race fix is99dc835.Hosted CI on the latest commit
All applicable checks passed on
99dc835: frontend tests/type checks/lint passed with 645 tests, backend and Google authentication checks passed, and frontend/backend container builds passed. Runner/documentation builds were skipped by path filters.