Platform

Platform overviewArchitectureWorkflow orchestrationGitOps configurationGovernance and AAAAI and MCPKnowledge and contextRuntime and executionEvidence and monitoring

Use cases

All use casesProduction incidentRelease preparationHotfix to productionSecurity scan triage
Why NopsAIIntegrationsSecurity

Resources

All resourcesAI agent governanceMCP governanceMCP securitySelf-hosted platforms
PricingGitHub

Company

How a run worksAboutContactBook a demo

Runs API

Starting runs, reading their state and logs, deciding their approvals, and handling their deliverables.

ReferenceDeveloperOperatorAutomation author

Key points

  • Starting a run answers with JSON only when the request sends Accept: application/json; otherwise it returns a plain-text confirmation.
  • Starting a run returns run_id, while rerunning returns runId. The two routes do not share a response shape.
  • Poll /status for a status, and /runs/{runID} with If-None-Match when you need detail — that route issues an ETag and answers 304 while nothing changed.
  • The run list filters on limit, offset, teamId, and branch. There is no pipeline filter, and passing teamId changes the response into a team-by-branch grouping.
  • Rerunning a run that is still in progress is refused with 409, and authorization is re-evaluated for whoever triggers the rerun.
  • Rejecting an approval requires a comment; approving does not.
  • Deliverables can be retried and cancelled per item without re-running the pipeline.

Operations

POST/v1/runAuthorized

Starts a run from an inline or referenced pipeline.

Notes

sensitive_variables names which of the supplied variables must be masked in logs. Passing a secret without naming it there puts it in the run record in clear text.

Parameters

NameInTypeRequiredDescription
X-Nopsai-ScopeheaderstringOptionalRun scope. Takes precedence over scope in the body.
AcceptheaderstringOptionalSend application/json to get a JSON body instead of a plain-text confirmation.Default: text/plain

Call it

Start a run from an inline definitionapi-runs request
curl -sX POST "$NOPSAI_URL/v1/run" \
  -H "Authorization: Bearer $NOPSAI_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  --data @run-request.json | jq
Result

A run id. Without Accept: application/json the same call answers with a plain-text confirmation.

Replace before running
  • run-request.json carries pipeline or definition, plus scope, variables, and sensitive_variables.

Responses

201application/json

The run was accepted and queued.

{
  "run_id": "9c1f7a5e-2b44-4d2f-8f2a-2c9f0b6d4e11",
  "trigger_event_id": ""
}

When it fails

StatusCauseWhat to do
400Invalid JSON, an unusable definition, or a variable override name outside ^[A-Za-z0-9_.-]+$.The message names the offending variable names rather than just rejecting the request.
403The caller may not use a resource the run touches — a model, an MCP profile, a scope, or the pipeline itself.Authorization covers everything the run would reach, not just the pipeline.
500The run could not be recorded or dispatched.Retry; check the dispatcher has a registered runner.

Side effects

  • Creates a durable run record before any work starts.
  • Submits the job to the dispatcher, which assigns it to a runner with matching scopes and free capacity.
  • Writes an audit record naming the caller and every resource the run was authorized to use.

Proven by

  • services/nopsai/run_status_test.go
  • services/nopsai/aaa_integration_test.go
  • services/nopsai/run_handlers.go
  • services/nopsai/run_launcher.go
POST/v1/run/{pipelineName...}Authorized

Starts a run of a stored pipeline.

Notes

The scope decides which variables and secrets resolve. Running the right pipeline in the wrong scope is the most common cause of a run that fails before its first step.

Parameters

NameInTypeRequiredDescription
pipelineNamepathstringRequiredStored pipeline identifier, including its team prefix.
X-Nopsai-ScopeheaderstringOptionalRun scope. Takes precedence over scope in the body.
AcceptheaderstringOptionalSend application/json for a JSON response body.Default: text/plain

Call it

Run a stored pipeline in a scopeapi-runs request
curl -sX POST "$NOPSAI_URL/v1/run/platform/release-service" \
  -H "Authorization: Bearer $NOPSAI_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"scope":"platform/production","variables":{"RELEASE_CHANNEL":"stable"}}' | jq -r .run_id
Result

The run id of the queued run.

Responses

201application/json

Run queued.

{
  "run_id": "9c1f7a5e-2b44-4d2f-8f2a-2c9f0b6d4e11",
  "trigger_event_id": ""
}

When it fails

StatusCauseWhat to do
400Invalid payload, unknown pipeline, or invalid variable override names.Confirm the identifier with GET /v1/pipelines.
403The caller may not run this pipeline or use something it references.Check /v1/access/effective-permissions for the pipeline and the scope.
500The run could not be recorded or dispatched.Retry.

Side effects

  • Creates a run record and submits it to the dispatcher.
  • Variable overrides are recorded on the run, so a run is reproducible from its own record.
  • Writes an audit record.

Proven by

  • services/nopsai/run_status_test.go
  • services/nopsai/run_handlers.go
GET/v1/runsAuthorized

Lists runs. Exposes only lightweight aggregate final_output_status metadata.

Notes

Passing teamId changes the response shape to a team-by-branch grouping. Code that parses both forms should branch on the parameter it sent.

Parameters

NameInTypeRequiredDescription
limitqueryintegerOptionalPage size. A value of zero or less becomes 50; anything above 1000 is capped at 1000.Default: 300
offsetqueryintegerOptionalRows to skip. Negative values are ignored.Default: 0
teamIdquerystringOptionalRestrict to one team. Changes the response into a team-by-branch grouping rather than a flat list.
branchquerystringOptionalRestrict to runs for one Git ref.

Call it

List recent runsapi-runs request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/runs?limit=20" | jq '.[] | {run_id, pipeline_name, status}'
Result

Runs the caller may see, newest first. There is no pipeline filter — select on pipeline_name in the response.

Responses

200application/json

A flat list of run records, filtered per caller.

[
  {
    "run_id": "9c1f7a5e-2b44-4d2f-8f2a-2c9f0b6d4e11",
    "pipeline_name": "release-service",
    "pipeline_path": "platform",
    "status": "success",
    "git_ref": "refs/heads/main",
    "started_at": "2026-08-19T10:44:02Z",
    "finished_at": "2026-08-19T10:47:18Z",
    "duration": "3m16s",
    "is_complete": true
  }
]

When it fails

StatusCauseWhat to do
400teamId is not a known team id or the root alias.Read team ids from GET /v1/teams.
503Authorization is unavailable, so runs cannot be filtered safely.Check AAA. The platform refuses rather than returning runs the caller may not see.
500The run query failed.Platform fault.

Side effects

  • None.

Proven by

  • services/nopsai/run_status_test.go
  • services/nopsai/run_handlers.go
GET/v1/runs/{runID}Authorized

Full run detail including steps, tasks, and outputs.

Notes

Poll with the ETag. Polling this route without If-None-Match rebuilds the whole detail document every time.

Parameters

NameInTypeRequiredDescription
runIDpathuuidRequiredRun identifier.
If-None-MatchheaderstringOptionalETag from a previous read. Matching returns 304 with no body, which is how the UI polls a live run cheaply.

Call it

Read run detail, then poll it cheaplyapi-runs request
ETAG=$(curl -sD - -o run.json -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/runs/$RUN_ID" | awk '/^ETag:/ {print $2}' | tr -d "\r")
curl -s -o /dev/null -w "%{http_code}\n" -H "Authorization: Bearer $NOPSAI_TOKEN" -H "If-None-Match: $ETAG" "$NOPSAI_URL/v1/runs/$RUN_ID"
Result

The first call returns the run with an ETag; the second returns 304 while nothing has changed.

Responses

200application/json

The run, its steps and tasks, its child runs, AI usage, and its final outputs.

{
  "run_id": "9c1f7a5e-2b44-4d2f-8f2a-2c9f0b6d4e11",
  "pipeline_name": "release-service",
  "status": "success",
  "steps": [
    { "name": "build", "status": "success" },
    { "name": "production-gate", "status": "success" }
  ]
}
304

Nothing changed since the ETag was issued.

When it fails

StatusCauseWhat to do
403The caller may not read this run.Run visibility follows the owning team.
404No run with that id.Check the id: a deleted run is gone, not hidden.
503Authorization is unavailable.Check AAA.
500Run detail could not be assembled.Platform fault.

Side effects

  • None.

Proven by

  • services/nopsai/run_status_test.go
  • services/nopsai/run_handlers.go
GET/v1/runs/{runID}/statusAuthorized

Compact run status for polling.

Notes

This is the route to poll in a script. It answers with one field and does not assemble the run graph.

Parameters

NameInTypeRequiredDescription
runIDpathuuidRequiredRun identifier.

Call it

Poll for completionapi-runs request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/runs/$RUN_ID/status" | jq -r .status
Result

One of queued, running, success, failure, warning, timed_out, or canceled.

Responses

200application/json

The current status and nothing else.

{"status":"running"}

When it fails

StatusCauseWhat to do
404No run with that id.Confirm the id from the run list.

Side effects

  • None.

Proven by

  • services/nopsai/run_status_test.go
  • services/nopsai/run_internal_handlers.go
GET/v1/runs/{runID}/logsAuthorized

Durable pipeline run logs.

Notes

Declared secrets, sensitive variable names, and outputs marked sensitive are masked before the response is written. A value a script printed itself cannot be masked, because the platform never knew it was one.

Parameters

NameInTypeRequiredDescription
runIDpathuuidRequiredRun identifier.
after_idqueryintegerOptionalReturn only records with a higher id, which is how a tail continues without re-reading.Default: 0
include_childrenquerybooleanOptionalInclude logs from child pipeline runs.Default: false

Call it

Read run logsapi-runs request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/runs/$RUN_ID/logs" \
  | jq -r '.[] | "\(.timestamp) \(.step_name // "-") \(.line)"' | tail -40
Result

Log records in id order. The text is under line, with step_name and task_name when the record came from a step.

Responses

200application/json

Log records, oldest first.

[
  {
    "id": 4211,
    "timestamp": "2026-08-19T10:44:09Z",
    "line": "building 1.0.1755600249",
    "step_name": "build",
    "stream": "stdout",
    "level": "info"
  }
]

When it fails

StatusCauseWhat to do
403The caller may not read this run.Log access follows run visibility.
500The log query failed.Platform fault.

Side effects

  • None.

Proven by

  • services/nopsai/run_status_test.go
  • services/nopsai/run_internal_handlers.go
  • pkg/models/types.go
POST/v1/runs/{runID}/cancelAuthorized

Cancels an active run.

Parameters

NameInTypeRequiredDescription
runIDpathuuidRequiredRun to cancel.

Call it

Cancel a runapi-runs request
curl -sX POST -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/runs/$RUN_ID/cancel" | jq
Result

The run moves to canceled. Work already running is stopped; work already finished stays recorded.

Responses

200application/json

Cancellation accepted.

{"status":"canceled"}

When it fails

StatusCauseWhat to do
400The id is malformed.Use the run id from the list route.
404No run with that id.Confirm the id.
405A method other than POST.Use POST.
500Cancellation could not be recorded.Retry; the run keeps running until it succeeds.

Side effects

  • Signals the agent to stop and releases the runner capacity the run held.
  • A pending approval on the run is closed out rather than left waiting.
  • Writes an audit record.

Proven by

  • services/nopsai/run_status_test.go
  • services/nopsai/run_handlers.go
  • doc/runtime-flows.md
POST/v1/runs/{runID}/rerunAuthorized

Starts a new run from the same definition and inputs.

Notes

A rerun repeats the definition, not the result. A pipeline whose definition changed since the original run reruns with the new definition.

Parameters

NameInTypeRequiredDescription
runIDpathuuidRequiredThe run to repeat.

Call it

Rerun a finished runapi-runs request
curl -sX POST -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/runs/$RUN_ID/rerun" | jq -r .runId
Result

A new run id. Note the field is runId here, while starting a run returns run_id.

Responses

201application/json

A new run was queued from the original definition, scope, and Git context.

{
  "runId": "b71e0f22-9a3d-4c81-9d5c-1f0a5d7e2c44",
  "triggerEventId": ""
}

When it fails

StatusCauseWhat to do
404The original run does not exist.Confirm the id.
409The original run is still in progress.Wait for it to finish. Rerunning a live run is refused rather than queued.
403The caller may not use a resource the rerun would touch.Authorization is re-evaluated for the caller now, not inherited from whoever started the original run.
405A method other than POST.Use POST.
500The rerun could not be created.Retry.

Side effects

  • Creates a separate run record; the original is untouched.
  • Writes an audit record naming the caller who triggered the rerun.

Proven by

  • services/nopsai/run_status_test.go
  • services/nopsai/run_handlers.go
DELETE/v1/runs/{runID}Authorized

Deletes a run record.

Notes

For bulk retention use the data cleanup routes, which preview before deleting. This route removes one record with no preview.

Parameters

NameInTypeRequiredDescription
runIDpathuuidRequiredRun to delete.

Call it

Delete a run recordapi-runs request
curl -sX DELETE -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/runs/$RUN_ID" -w "%{http_code}\n"
Result

204. The record, its logs, and its outputs go with it.

Responses

204

Deleted.

When it fails

StatusCauseWhat to do
400The id is malformed.Use a run id.
404No run with that id.It may already be deleted.
500The delete could not be persisted.Retry.

Side effects

  • Removes the run, its logs, and its generated outputs permanently.
  • Writes an audit record.

Proven by

  • services/nopsai/run_status_test.go
  • services/nopsai/run_handlers.go
GET/v1/runs/{runID}/approvalsAuthorized

Lists approval checkpoints on a run.

Notes

expires_at is when the checkpoint times out. An expired approval produces a timed_out run, not a failed one.

Parameters

NameInTypeRequiredDescription
runIDpathuuidRequiredRun identifier.

Call it

List approvals on a runapi-runs request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/runs/$RUN_ID/approvals" | jq
Result

Each checkpoint with its assigned teams, status, requester, decision, and expiry.

Responses

200application/json

Approval records for the run.

[
  {
    "run_id": "9c1f7a5e-2b44-4d2f-8f2a-2c9f0b6d4e11",
    "step_name": "production-gate",
    "task_name": "production-gate",
    "approval_type": "production-release",
    "assigned_teams": ["platform/sre"],
    "allow_self_approval": false,
    "status": "pending",
    "requested_at": "2026-08-19T10:46:10Z",
    "expires_at": "2026-08-20T10:46:10Z"
  }
]

When it fails

StatusCauseWhat to do
403The caller may not read this run.Approval visibility follows run visibility.
503Authorization is unavailable.Check AAA.
500The approval query failed.Platform fault.

Side effects

  • None.

Proven by

  • services/nopsai/approval_handlers_test.go
  • services/nopsai/approval_visibility_test.go
  • services/nopsai/approval_handlers.go
POST/v1/runs/{runID}/approvals/{approvalID}/approveAuthorized

Approves a checkpoint. Comment optional.

Parameters

NameInTypeRequiredDescription
runIDpathuuidRequiredRun holding the checkpoint.
approvalIDpathstringRequiredCheckpoint identifier from the approvals list.

Call it

Approve a checkpointapi-runs request
curl -sX POST "$NOPSAI_URL/v1/runs/$RUN_ID/approvals/$APPROVAL_ID/approve" \
  -H "Authorization: Bearer $NOPSAI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"comment":"Change 4821 signed off"}' | jq
Result

The checkpoint moves to approved and the run resumes from where it paused.

Replace before running
  • comment is optional when approving and required when rejecting.

Responses

200application/json

Decision recorded and the run released.

{
  "step_name": "production-gate",
  "status": "approved",
  "decided_by_email": "[email protected]",
  "decided_at": "2026-08-19T10:52:03Z",
  "decision_comment": "Change 4821 signed off"
}

When it fails

StatusCauseWhat to do
400The body is malformed, or the checkpoint is no longer pending.Re-read the approvals list: a decided or timed-out checkpoint cannot be decided again.
403The caller is not in an assigned team, or is the requester and allow_self_approval is false.Self-approval is a manifest decision, not an authorization override.
404No such run or checkpoint.Confirm both ids.

Side effects

  • Releases the paused run to continue.
  • Records who decided, when, and with what comment, permanently on the run.
  • Writes an audit record.

Proven by

  • services/nopsai/approval_handlers_test.go
  • services/nopsai/approval_handlers.go
  • services/nopsai/approval_schema.go
POST/v1/runs/{runID}/approvals/{approvalID}/rejectAuthorized

Rejects a checkpoint. Comment required.

Notes

Approval failures always fail closed. ignore_failure on the step does not apply to a rejected gate.

Parameters

NameInTypeRequiredDescription
runIDpathuuidRequiredRun holding the checkpoint.
approvalIDpathstringRequiredCheckpoint identifier from the approvals list.

Call it

Reject a checkpointapi-runs request
curl -sX POST "$NOPSAI_URL/v1/runs/$RUN_ID/approvals/$APPROVAL_ID/reject" \
  -H "Authorization: Bearer $NOPSAI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"comment":"Blocked: pending incident 2291"}' | jq
Result

The checkpoint is rejected and the run fails closed at the gate.

Replace before running
  • comment is required here — rejecting without one returns 400.

Responses

200application/json

Rejection recorded.

{
  "step_name": "production-gate",
  "status": "rejected",
  "decided_by_email": "[email protected]",
  "decided_at": "2026-08-19T10:53:41Z",
  "decision_comment": "Blocked: pending incident 2291"
}

When it fails

StatusCauseWhat to do
400No comment was supplied, or the checkpoint is no longer pending.The message is comment is required when rejecting an approval. A rejection without a reason is not accepted.
403The caller is not in an assigned team.Only assigned teams decide.
404No such run or checkpoint.Confirm both ids.

Side effects

  • Fails the run closed at the checkpoint.
  • Records the decision and its comment permanently.
  • Writes an audit record.

Proven by

  • services/nopsai/approval_handlers_test.go
  • services/nopsai/approval_handlers.go
GET/v1/runs/{runID}/outputs/{outputID}/downloadAuthorized

Downloads generated final output content.

Parameters

NameInTypeRequiredDescription
runIDpathuuidRequiredRun that produced the deliverable.
outputIDpathstringRequiredOutput identifier from run detail.

Call it

Download a deliverableapi-runs request
curl -s -OJ -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/runs/$RUN_ID/outputs/$OUTPUT_ID/download"
Result

The generated file, with the content type of its declared output type.

Responses

200application/octet-stream

The generated content: Markdown, JSON, HTML, PDF, or a spreadsheet, depending on the item type.

When it fails

StatusCauseWhat to do
400The ids are malformed.Take both ids from run detail.
403The caller may not read this run.Generated content stays on authorized detail paths; run lists only expose an aggregate status.
404No such run or output.Confirm the output id.
409The output has not been generated, or generation failed.Check its status in run detail and retry generation before downloading.
500The content could not be read.Platform fault.

Side effects

  • None.

Proven by

  • services/nopsai/pipeline_final_output_contract_test.go
  • services/nopsai/pipeline_final_output_specs.go
  • doc/final-output-rendering.md
POST/v1/runs/{runID}/outputs/{outputID}/retryAuthorized

Retries a failed final output.

Notes

Provider and network errors are not retried automatically, which is why this route exists: a renderer failing is not the work failing.

Parameters

NameInTypeRequiredDescription
runIDpathuuidRequiredRun that owns the deliverable.
outputIDpathstringRequiredOutput identifier from run detail.

Call it

Retry a failed deliverableapi-runs request
curl -sX POST -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/runs/$RUN_ID/outputs/$OUTPUT_ID/retry" | jq
Result

Generation restarts for that item alone. The pipeline is not re-run.

Responses

200application/json

Retry accepted.

{"status":"pending"}

When it fails

StatusCauseWhat to do
400The ids are malformed.Take both from run detail.
404No such run or output.Confirm the output id.
409The output is already generating or already succeeded.Cancel it first if you need to regenerate.
500The retry could not be queued.Retry.

Side effects

  • Re-runs generation for one item; the pipeline itself is not executed again.
  • Writes an audit record.

Proven by

  • services/nopsai/pipeline_final_output_contract_test.go
  • services/nopsai/pipeline_final_output_specs.go
POST/v1/runs/{runID}/outputs/{outputID}/cancelAuthorized

Cancels in-progress final output generation.

Parameters

NameInTypeRequiredDescription
runIDpathuuidRequiredRun that owns the deliverable.
outputIDpathstringRequiredOutput identifier from run detail.

Call it

Cancel a deliverable that is still generatingapi-runs request
curl -sX POST -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/runs/$RUN_ID/outputs/$OUTPUT_ID/cancel" | jq
Result

Generation stops and the item is marked cancelled. The run status itself is unaffected.

Responses

200application/json

Cancellation accepted.

{"status":"canceled"}

When it fails

StatusCauseWhat to do
400The ids are malformed.Take both from run detail.
404No such run or output.Confirm the output id.
409The output is not generating.Only in-progress generation can be cancelled.
500The cancellation could not be recorded.Retry.

Side effects

  • Stops generation for one item.
  • Writes an audit record.

Proven by

  • services/nopsai/pipeline_final_output_contract_test.go
  • services/nopsai/pipeline_final_output_specs.go
GET/v1/runs-by-check/{checkRunID}Authorized

Resolves the run behind a GitHub check run.

Parameters

NameInTypeRequiredDescription
checkRunIDpathstringRequiredGitHub check run id, as it appears in the check URL.

Call it

Find the run behind a checkapi-runs request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/runs-by-check/$CHECK_RUN_ID" | jq -r .run_id
Result

The run id, so a reviewer landing on a failing check can reach the run that produced it.

Responses

200application/json

The run linked to that check run.

{
  "run_id": "9c1f7a5e-2b44-4d2f-8f2a-2c9f0b6d4e11",
  "pipeline_name": "release-service",
  "status": "failure"
}

When it fails

StatusCauseWhat to do
404No run is linked to that check run id.The check may predate the link, or belong to another install.

Side effects

  • None.

Proven by

  • services/nopsai/run_status_test.go
  • services/nopsai/run_handlers.go
DELETE/v1/repositories/{repoOwner}/{repoName}/branches/{branch...}Authorized

Deletes runs recorded for a deleted branch.

Notes

Housekeeping after a branch is merged and deleted. It is bulk and irreversible: there is no preview.

Parameters

NameInTypeRequiredDescription
repoOwnerpathstringRequiredRepository owner.
repoNamepathstringRequiredRepository name.
branchpathstringRequiredBranch name. A catch-all segment, so a branch with slashes is passed as-is.

Call it

Clear runs for a deleted branchapi-runs request
curl -sX DELETE -H "Authorization: Bearer $NOPSAI_TOKEN" \
  "$NOPSAI_URL/v1/repositories/acme/payments/branches/feature/new-checkout" -w "%{http_code}\n"
Result

204. Every run recorded for that branch is removed.

Responses

204

Runs for the branch were deleted.

When it fails

StatusCauseWhat to do
400The owner, repository, or branch is empty.All three segments are required.
500The delete could not be persisted.Retry.

Side effects

  • Deletes every run recorded for that branch, with their logs and outputs.
  • Writes an audit record.

Proven by

  • services/nopsai/run_status_test.go
  • services/nopsai/run_handlers.go

How it works

Every write here is authorized against more than the pipeline. Starting a run checks the caller against each resource the run would touch — the model, the MCP profiles, the scope — which is why a 403 can name something the caller did not think they were using.

Log masking covers what the platform knows to be secret: declared secrets, names listed in sensitive_variables, and outputs marked sensitive. A value a script prints itself cannot be masked, because nothing declared it.

The two bulk deletes on this page — deleting a run and clearing a branch — have no preview and no undo. Retention at scale belongs in the data cleanup routes, which preview first.

Implementation evidence

  • services/nopsai/run_handlers.go

    Run start, list, detail, cancel, rerun, and delete handlers.

  • services/nopsai/approval_handlers.go

    Approval listing and decision handling.

  • services/nopsai/run_internal_handlers.go

    Status and log responses.