Skip to content

feat(runners): a code repository picks its Runner; Runners lose Runs AI Code Fixes - #5086

Merged
simlarsen merged 12 commits into
masterfrom
claude/runner-choice-code-remediation
Oct 20, 2026
Merged

simlarsen merged 12 commits into
masterfrom
claude/runner-choice-code-remediation

Conversation

@simlarsen

@simlarsen simlarsen commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

What

From the maintainer's feedback doc: "Remove Runs AI Code Fixes from here, and in the Code Repo section, we should be able to select a runner to run from (if not selected, it will run on global runner)."

  • A code repository picks its Runner. Code → a repository → Settings → Edit Settings has a new Runner step with the one Runner picker: Global Runner first (the Runner OneUptime runs for every project; on a self-hosted install, the bundled runner service), then the project's own Runners. Empty means Global Runner. The settings card says what the repository runs on, with how it is doing, or why there is nothing to run on.
  • A Runner has no "Runs AI Code Fixes" switch - gone from the Create Runner dialog, the Runner's page and the Runners list. A Runner takes code work exactly while a repository of its project picks it; its page now lists those repositories (a new Code Repositories card, each linked to the repository's settings).
  • The server enforces it. One pure rule (Common/Types/Runner/CodeRepositoryRunner) decides who may work in a repository; the server reads it for the code-fix claim and the queue count (CodeFixClaimQuery joins each run to its repository), the repository token (only the repository's Runner, or a global Runner for a repository that picks none, is given one), the Runner's heartbeat capability, Code's gate and blockers, the exception page's checklist and the queue sweeper. A repository can only pick one of its own project's Runners (the project reference check), never the in-cluster Runner the Kubernetes agent chart installed.
  • Runs know their repository from the start where the server can decide it (exception fixes from their readiness, subject/trace/service recipes through the same resolver their details route uses), so the right Runner takes them.
  • Migrations. 1804300000000-AddCodeRepositoryRunner adds the column and carries today's choice over: each project's repositories pick the Runner it had the switch on for (alive most recently, never an agent row, a deleted, a global or a never-connected one), then the switch's column goes; down() gives the switch back to every picked Runner. 1804350000000-AddCodeRepositoryRunnerIndexAndKey builds the column's index and its key to Runner online (OnlineDdl, outside a transaction), so repositories stay writable while it runs.
  • "Use global Runners" now covers code repositories too (one meaning of "global Runner" in the product): with it off, a repository that picks none needs one of the project's Runners, and Code says so.
  • Left alone on purpose: Runs AI Remediation Commands and the remediation Runner choice belong to the next task (remediation-ai-runbook-allowlist). Runs Runbooks stays: it is the host owner's consent for runbook steps, which the claim enforces.

Tests

  • Common: the pure rule, the claim SQL condition, the server reader, run → repository at enqueue, token refusal, claim/count per worker, /runner/global-code-runners, AIAgentService readers, the repository's Runner setting (project check, agent-row refusal, ACL), both migrations statement-by-statement, and an opt-in Postgres suite that runs both on seeded rows (agent predicate checked row by row against RunnerService.isKubernetesAgentRunnerRow; the key validated and the index valid, also when run again; mutation-checked) - wired into postgres-schema-drift.yaml. All migrations on a fresh database: no schema drift. Dashboard: the picker for a repository, its loader, the Settings field and card, the Runner page card; the one-rule guard now covers repositories.
  • App: ingress heartbeat capability from the repository pick, registration refusals, wrong-key HTTP suite, seed, docs facts.
  • Offline E2E: Code (Settings Runner step on desktop and phone, saves, blockers' "Pick a Runner" link) and Global Runners (Create Runner without the switch, the Runner page's Code Repositories card).
  • Docs in all 17 languages: Code (new "Pick the repository's Runner" section), GitHub App, Runbook Agents, the 13 → 14 upgrade note; Runner README.

UI review

Looked at, in real Chromium through the offline Code and Global Runners fixtures (and the exception page's), at 1280px and 390px, light and dark, in English and German:

  • Code → a repository → Settings: the card, and every state of Edit Settings' Runner step - Global Runner connected and disconnected, one of the project's Runners, global Runners turned off, a server with no global Runner, a pick the reader cannot see - with the menu open and closed.
  • The repository's request box and a task's conversation with each Runner blocker (NoAgent, RunnerNotOnline, GlobalRunnersOff), for an owner and a member.
  • Runbooks → Runners: the list, the global Runner cards, the switch on and off; Create Runner's Capabilities step; a Runner's page with and without repositories that pick it.
  • The exception page's AI readiness checklist (ready, and not set up).

What changed, and why:

  • Edit Settings → Runner is a field like the dialog's others. It drew the runbook step editor's compact look - a 12px "Runner" label of its own and 12px lines under the menu - in a dialog whose fields have a 14px title with their description above the control. The form now draws Runner and Where AI works in this repository. (the card's own words) above the menu, with no "(Optional)" since empty is Global Runner, an answer of its own; the picker's lines are the form's size, and the menu is named by the form's label (aria-labelledby). While its choices load it holds a menu-sized placeholder instead of the page loader, which pushed the step down 130px and then jumped. The runbook step editor keeps its compact picker (RunnerPicker isFormField).
  • The settings card draws the Runner's status as the Runners page does: a green, red or grey dot (RunnerLiveStatusBubble, which RunnerStatusElement now draws through too) instead of grey "· Connected" text; one of the project's Runners links to its page.
  • On Global Runner, a repository with setup, build or test commands is told they are skipped ("Run by OneUptime for every project. It skips your setup, build and test commands."), right above those commands, which the card lists and which otherwise read as if they run. Not shown without commands, or on one of the project's Runners.
  • A pick the page cannot name is "A Runner you do not have access to", in the value's own colour - not "Unknown Runner (60000000-…)" in the menu and an amber "A Runner this project no longer has" on the card. A repository can only pick its project's own Runners, and a deleted Runner leaves it on Global Runner (SET NULL), so in practice this means the reader may not read Runners (a Settings Member) - the pick itself is fine. For the same reader, RunnerChoicesLoader.loadForCodeRepository now reads the project's Runners on their own: one refused read failed the whole load, and the card said "None: this server runs no global Runner".
  • No global Runner on the server and nothing picked: the step said what a picked Runner does ("AI clones the repository on this Runner…") under an empty "Select a Runner". It now says to pick one of the project's own.
  • RunnerNotOnline names the Runner: "The Runner this repository picks, prod-eu-west-1, is not online. Check that its container is running, or pick another Runner." The server fills a new optional CodeTaskBlocker.runnerName (its English message already named it); without one the unnamed sentence stays.
  • A Runner's page lists the repositories that pick it in Runner Details, after Runs AI Remediation Commands - where the Runs AI Code Fixes switch was - instead of a card of its own that sat between Runner Status and the setup command on every runbook Runner, saying "AI works in these repositories on this Runner." over "No code repository picks this Runner…". With none: "None. A repository picks its Runner in its settings in Code." Not drawn on the Kubernetes agent's Runner, nor for someone who cannot read repositories. The Setup Instructions line now reads "For each code repository that picks this Runner, it clones the repository for AI and opens pull requests for review…".
  • Setup Instructions show on a phone. The card carried the install command in its description, which a card hides below md (on master too: a phone showed the title and nothing under it), and a description is a paragraph, so the command's block was nested in it (three validateDOMNesting warnings). It is the card's body now.
  • The orphaned run message says what to do once: "…or try again once the global Runner is back. Then start the task again." is now the reason alone; the failed turn has its own Try again.
  • Copy: 6 new strings translated in all 16 languages (Spanish in tú, French "agent Runbook" as the Dashboard says it, each language's own word for repository), 5 retired.

Tests added or changed in the review:

  • Common/Tests/App/Dashboard/CodeRepositoryRunnerPicker.test.tsx: the picker as a form field (no label of its own, named by the form's label, form-size lines; negative control for the step editor), a pick the reader cannot see (no id, no warning colour), no global Runner with nothing picked, a refused Runner read keeping the global Runners and the switch, the card's status dot (connected, disconnected; still), the skipped-commands line and its negative controls, line- and menu-sized loading placeholders.
  • CodeRepositorySettingsRunner.test.tsx: the field is drawn by the form (description, no "(Optional)", aria-labelledby passed through) and the card passes hasCommands; the Runner page's loader, list and empty line, the row's place in Runner Details (after the capabilities, before Labels, not for agent rows or unreadable repositories), Setup Instructions as the card's body.
  • RunnerStatus.test.tsx: RunnerLiveStatusBubble - its three statuses, the same colours a Runner row draws, a still connected dot, a suffix.
  • CodeConversationParts.test.tsx: RunnerNotOnline names the Runner in bold; a name on another blocker is never drawn. CodeTaskCopy.test.ts: the named sentence is the unnamed one with the name in it, translated in all 16 languages.
  • Server: CodeFixReadiness.test.ts (the blocker carries runnerName; an unknown name is left out), CodeTaskGate.test.ts, CodeFixRunQueue.test.ts (the reason said once).
  • App RunnerUi.test.ts: the Runner page's new strings in every locale.
  • Offline E2E: Code (the Runner step as a form field with its accessible name, the status dot, the skipped-commands line and its absence on prod-eu-west-1, no global Runner, a Runner the reader cannot see, the named blocker), Global Runners (the Runner Details row and its place, the empty line, no card of its own, Setup Instructions on a phone).

Left as they are (on master already, outside this change): the Runners list's Capabilities column runs under the sticky actions column at 1280px; the top pixel of a helper line shows between a select and its open menu (shared Dropdown); "Step 3 of 4" is not translated in a stepped dialog on a phone; Reset Runner Key nests a paragraph in a paragraph (Common ResetObjectID).

Run after the review, on the branch with the newest master merged (no rebase, no force-push):

  • Offline E2E: Code 84 passed (20 width-independent skips), Global Runners 30 passed.
  • Common jest: the 17 Code, Runner and picker suites above plus the server suites (534 + 50 tests), the guard suites (npx jest Guard: 151 suites, 4,764 tests), the locale suites (31 suites, 1,173 tests).
  • App jest: RunnerUi, RunnersLiveUnderRunbooks, DashboardLocalesGuard, RunnerVersionLabel, AgentVersionDisplayGuard (561 tests), the root-level suites (17 suites, 263 tests), the locale suites (9 suites, 557 tests).
  • The suites this review changed, again with ee/ moved aside (and put back; nothing committed meanwhile).
  • tsc --noEmit for Common, the Dashboard and E2E; Common npm run compile-tests (all three parts); App tsc. E2E's --listFiles reaches only itself and Common.
  • npm run i18n:check: every locale sound, no translation lost. i18n:extract --dry-run adds only 35 model titles that are missing on master already (SCIM, SSO, Vercel), none of this change's.
  • Prettier --check on every code, test and data file the branch changes: clean except packages/Runner/README.md, which master's own copy fails too (its tables; CI lints .ts/.tsx only). ESLint on every .ts/.tsx/spec file this review changed: clean (the offline fixtures' .js are outside every tsconfig, as on master).
  • ReDoS checker: no new patterns.
  • Not run: a live stack; the full Common and App jest runs (CI runs them on master).

🤖 Generated with Claude Code

valeriamustafaeva and others added 10 commits October 20, 2026 01:58
…tch on a Runner

A repository's settings in Code now pick the Runner AI works in it on
(CodeRepository.runnerId), and a repository that picks none runs on a
global Runner - the one OneUptime runs for every project - while its
project uses global Runners. A Runner no longer has a "Runs AI Code Fixes"
switch: it takes code work exactly while a repository of its project picks
it.

- One pure rule (Types/Runner/CodeRepositoryRunner) decides who may work
  in a repository; the server reads it for the code-fix claim
  (CodeFixClaimQuery joins each run to its repository), the repository
  token (only the repository's Runner gets one), the Runner's heartbeat
  capability, Code's gate, the exception checklist and the queue sweeper.
- Runs are queued on their repository where the server can decide it:
  exception fixes from their readiness resolution, subject, trace and
  service recipes from the same facts their details route reads
  (CodeFixRunRepository).
- The migration carries today's choice over: a project's code-fix Runner
  becomes the Runner of every repository it has, then the old column goes.
- The Runner dialog and page lose the switch; a Runner's page lists the
  repositories that pick it; Code's repository settings get a Runner step
  with the one Runner picker; Code's blockers say which Runner is missing
  and link the repository's settings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d text in 17 languages

- The claim, queue, readiness, gate, token and Kubernetes AI access suites
  read the repository's Runner choice instead of a Runner's code-fix switch;
  the claim's tenancy suite pins the candidate SQL a project's Runner and a
  global Runner are given, and the re-read by id.
- New suites: the pure rule (Types/Runner/CodeRepositoryRunner) and the
  claim's SQL condition (CodeFixClaimQuery).
- The Dashboard's 27 new sentences are translated in all 16 languages; the
  13 this change no longer shows are gone from every locale.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The migration reads agent Runners with the predicate an earlier
  migration holds to the server's rule (IS_KUBERNETES_AGENT_RUNNER_SQL), and
  a new opt-in Postgres suite runs it on seeded rows - wired into the schema
  drift workflow - besides a statement-level suite.
- New suites for the server reader (CodeRepositoryRunnerChoice), the run's
  repository at enqueue (CodeFixRunRepository, SubjectCodeFixRun), the
  repository token refusal, the claim and queue count per worker, the
  global Runners for code route and AIAgentService readers, and the
  repository's Runner setting (project check, agent Runner refusal, ACL).
- Dashboard suites for the picker for a repository, its loader, the
  Settings field and card, and a Runner's Code Repositories card; the
  one-rule guard covers repositories too.
- App's ingress, heartbeat and registration suites read the repository
  pick instead of the dropped switch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e fix switch

- Code: a repository's Settings say what it runs on (Global Runner and how
  it is doing, a Runner of the project's own linked to its page, or why
  there is none); Edit Settings' Runner step offers Global Runner first,
  then the project's own Runners but never the Kubernetes agent's, and
  saves the Runner picked or none; the Runner blockers link to those
  settings. The fixture serves the global Runners for code, the project's
  Runners and switch, and saves the form as the server is sent it.
- Global Runners: Create Runner asks only what a Runner may run, the list
  shows no AI code fix capability, and a Runner's page lists the code
  repositories that pick it (or says none does), each linked to its
  settings in Code; the switch's words name code repositories too.
- The exception page's fixture uses the server's current Runner check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…uage

- Code (ai/ai-agent): a new "Pick the repository's Runner" section - the
  Runner step of a repository's Edit Settings, Global Runner when it picks
  none, what each runs, Use global Runners - and the steps, prerequisites,
  verification and failure words follow the repository's Runner instead of
  the Runs AI Code Fixes switch.
- GitHub App, Runbook Agents (no switch row; code fixes are not a
  capability; the override's words), and a 13 -> 14 upgrade note on what the
  migration carries over, with the switch named as it was in the older
  notes of every translation.
- Runner README: the third kind of work follows the repositories that pick
  the Runner.
- Docs suites hold it: the Code page facts, the switch kept in English as a
  removed setting, the new note's place and claims, and the Runner form's
  capabilities.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…bal jest

- The two Dashboard suites (the picker for a repository and the Settings
  page's Runner field) take jest itself from the global, as the other
  Dashboard suites that name jest.SpiedFunction do: @jest/globals types
  jest.spyOn() as jest-mock's spy, which the global annotation does not
  accept, so npm run compile-tests failed on them.
- The exception page's readiness suite reads the refusal's message with
  contains checks instead of a regular expression.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- installation/upgrading (17 languages): master's note that visitors keep
  their own address now follows the runbook variables note; the note that a
  code repository picks its Runner follows it, before the endpoint pointer,
  with its link's anchor localized again.
- RunbookVariablesUpgradeDocs is master's (its next note is the visitors
  note); VisitorAddressesUpgradeDocs now expects this change's note after it,
  and CodeRepositoryRunnerUpgradeDocs the visitors note before it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the Runner step plainly

- Prettier over six files the change touched (Runner model, Runner and
  AI agent data APIs, RunnerService, CodeFixReadiness, the Dashboard's
  RunnerChoices) and one import instead of two in
  CodeRepositoryRunnerChoice (no-duplicate-imports).
- The Runner README said "Settings → Edit Settings → Runner", which reads
  as the old Settings → Runners path RunnersLiveUnderRunbooks keeps out of
  the shipped source; it now says Edit Settings, then its Runner step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…a migration of their own

SchemaMigrationsOnlineDdl refuses a new migration that builds an index or
adds a foreign key on a table that already exists inside its transaction:
both block the table's writes while they scan it. CodeRepository holds every
project's repositories, so:

- AddCodeRepositoryRunner1804300000000 keeps the new nullable column (added
  without a rewrite), the backfill and dropping the switch.
- AddCodeRepositoryRunnerIndexAndKey1804350000000, registered right after
  it with transaction = false, builds the index with OnlineDdl.createIndex
  and adds the key to Runner with OnlineDdl.addForeignKey (NOT VALID, then
  validated), with the generated names, so the drift check finds nothing.
- The statement suite records both (the online ones through OnlineDdl), and
  the Postgres suite runs both on seeded rows - the second outside a
  transaction, then again as a stopped run would - and checks the key is
  validated and the index valid. Each way of breaking the second migration
  (no key, no index, a key that does not set the Runner to null) fails it.
- OwnedScopeEveryKindWithOwners lists CodeRepository.runner with its
  reason: a repository picks the Runner its AI works on, and Runners are
  read in full, so they can be picked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Dashboard locales (17 languages): master's new keys (the finding strip
  and the improvement card) kept in master's order; this change's 27 keys
  appended and its 13 removed keys dropped, merged key by key from the
  common base - neither side changed a key the other removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@simlarsen

simlarsen commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor Author

✅ Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
✅ Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

valeriamustafaeva and others added 2 commits October 20, 2026 05:40
…Runbooks

UI review of the repository Runner choice:

- Edit Settings' Runner step is a field like the dialog's others: the form
  draws its title and the card's description above the menu (no
  "(Optional)": empty is Global Runner), the picker no small label of its
  own, and its lines are the form's size. The menu is named by the form's
  label. While the choices load it holds a menu's room, not the page loader.
- The settings card draws the Runner's status as the Runners page does (a
  green, red or grey dot); on Global Runner a repository with setup, build
  or test commands is told they are skipped, right above them.
- A pick the page cannot name is a Runner the reader does not have access
  to (a repository can only pick its project's Runners, and a deleted one
  leaves it on Global Runner): said so, not "Unknown Runner (<id>)" or "a
  Runner this project no longer has". A refused Runner read no longer
  wipes out the global Runners, so a Settings Member is not told the server
  runs none.
- A repository that picked none on a server with no global Runner is told
  to pick one of its own, not what a picked Runner does.
- RunnerNotOnline names the Runner (CodeTaskBlocker.runnerName): "The
  Runner this repository picks, prod-eu-west-1, is not online."
- A Runner's page lists the repositories that pick it in Runner Details,
  where the Runs AI Code Fixes switch was, instead of a card that sat empty
  between Runner Status and the setup command; with none, one short line
  says where a repository picks its Runner.
- Setup Instructions are the card's body: as a description they were hidden
  on phones and nested blocks in a paragraph.
- The orphaned run message says what to do once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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