Repository navigation
Auto remediation: OneUptime AI fixes by default, runbooks become an allow-list, a rule picks its Runner - #5096
Merged
Conversation
…only the allowed ones; a rule picks its Runner An auto remediation rule no longer asks who fixes. OneUptime AI fixes what it matches through the AI agent on the affected cluster or host, picks the runbook that fits (any of the project's, or only the rule's runbooks when it names some) and composes commands on the rule's Runner when it picks one. A rule that runs its runbooks every time keeps doing that. - AutoRemediationFix: the one reading of how a rule fixes, its runbooks and its Runners (no Runner named is no host, never any Runner). - AutoRemediationRule.runnerId: the Runner OneUptime AI runs the rule's commands on. Picking it - or binding it to a cluster's AI access - is what lets OneUptime AI's commands run there (RunnerAiCommandsPick); the Runner's own switch (canRunAiCommands) is written by OneUptime only. - Engine: OneUptime AI rules and the no-rule default pick a runbook; a pick from the runbooks a Fix-without-asking rule names starts right away when no AI agent round is fixing the signal and the hourly breaker allows it. - Migrations 1804400000000/1804450000000: the column, carry-over of every rule's Runners and cluster bindings, online index and key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d its Runner fold under More fields The rule's Fix step no longer asks "Fix With". It asks one question, Approval, and folds what most rules never change under More fields: the runbooks OneUptime AI may run (none picked: any runbook), "Run them every time" (shown once the rule names runbooks), and the Runner OneUptime AI runs the rule's commands on, picked with the one Runner picker (none: the AI agent on the affected cluster or host; a global Runner never runs OneUptime AI's commands). The same for alert rules. Runners lose their "Runs AI Remediation Commands" switch: Create Runner is one page with "Runs Runbooks" and the labels under More fields, and Runner Details no longer shows it. Picking a Runner - as a rule's Runner, or for a Kubernetes cluster's AI access - is what lets OneUptime AI's commands run on it, and asks what the switch asked: a picker who may not read runbook credentials may not pick a Runner that holds SSH credentials, under the project's credential-reach lock. The cluster's AI page reads its Runners through RunnerChoicesLoader. - The migration changes no Runner's switch: a cluster bound to a Runner with it off keeps it off, and its AI page says to choose the Runner again. - The project's own runbook pick (no rule) records AiPickingRunbook / AiRunbookPickNotStarted, classified as acted / needs attention. - Dashboard locales: 26 new strings in 17 languages, 15 retired. - Docs in 17 languages: AI SRE rules section, Runbook Agents, AI Access, permissions, workflow steps, incident settings, and an upgrade note. - Tests: the fix reading, the Runner pick, the engine's lanes, the planning run's start, the migration (unit and Postgres, wired into the drift workflow), the rule service's Runner, the rule form and its fields, the Runner form, and an offline Playwright suite (test-auto-remediation-rules-ui, group 6 of Dashboard Offline UI). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ai-runbook-allowlist Master added 1804400000000-AddFixProductionVerification, so the rule Runner migrations move after it: 1804500000000-AddAutoRemediationRuleRunner and 1804550000000-AddAutoRemediationRuleRunnerIndexAndKey. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… "Run them every time" has its own description - RemediationPlanRunner: the rationale of a runbook OneUptime AI picked to start right away but proposed instead (the rule changed, the hourly breaker, or the runbook would not start) is written with mdText - the note, then OneUptime AI's reasoning as it was posted (FeedMarkdown.asMarkdown) - as FeedAndChatPlainTextEscapedGuard asks. The stored text is the same. - Its tests pin the whole rationale, with posted reasoning that holds Markdown, instead of a regular expression built by a hand escape (TestCodeScanningGuard). - The rule form's "Run them every time" field carries its own short description, so ShortDescriptionsGuard does not read the model column's. - RemediationExecutionRunner's cluster-status fixture says what the server now says for a Runner bound before picking one turned on its commands. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Author
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
…ai-runbook-allowlist-2
…d out of sight A OneUptime AI rule's runbooks now say which runbooks OneUptime AI may run for it, and - for a rule that fixes without asking - start with nobody approving them. The Fix With form hid the Runbooks field unless Fix With was Runbooks, yet still saved what the field held, so a rule switched from Runbooks to OneUptime AI kept runbooks nobody saw. Left in place, an upgrade would have started them unattended. The migration drops them (DROP_RUNBOOKS_THE_FORM_HID): such a rule reads as every OneUptime AI rule that names none - any runbook, always proposed for approval. A rule that runs its runbooks every time, and the two shapes saved before rules were simplified (AI picks a runbook, AI composes commands), keep theirs. Tests: the statement and its place in up() (unit), and on real rows - OneUptime AI rules asking first or not, deleted or not, lose their hidden runbooks; every-time, AI-picks and AI-commands rules keep theirs; the rules and their Runner are untouched. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… rule update's projects through the update path Two repo-wide guards the full Common run caught: - HookWritesCallersMayMake: RunnerService's create hook wrote canRunAiCommands = false for a global Runner. The column takes no one's create permission now (a Runner is picked for OneUptime AI's commands), and a value a create hook writes is asked of the caller as if they had sent it. The hook no longer writes it: a global Runner created to run them is still refused, and otherwise the column keeps its default, off (NOT NULL DEFAULT false). - UpdateChecksReadHeldRows: AutoRemediationRuleService.allowPickedRunners read the rules of an update with no tenant by the update's query itself. The projects a picked Runner is allowed in now come from the update path's readers - findProjectsToCheckUpdateIn, or getProjectsToCheckRowsIn on the rules already read - which hold the update to the rules read. A master admin's update made in another project now allows the Runner in the project of the rule it writes, not the request's. Tests: the global Runner create hook leaves the column to its default and keeps a false that was sent; a master admin's update picks the Runner in the rule's project; an update that picks no Runner reads no rule for it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ai-runbook-allowlist-2
…ai-runbook-allowlist-2 # Conflicts: # .github/workflows/test.app.yaml # packages/App/FeatureSet/Dashboard/src/Locales/da.json # packages/App/FeatureSet/Dashboard/src/Locales/de.json # packages/App/FeatureSet/Dashboard/src/Locales/en.json # packages/App/FeatureSet/Dashboard/src/Locales/es.json # packages/App/FeatureSet/Dashboard/src/Locales/fa.json # packages/App/FeatureSet/Dashboard/src/Locales/fr.json # packages/App/FeatureSet/Dashboard/src/Locales/hi.json # packages/App/FeatureSet/Dashboard/src/Locales/it.json # packages/App/FeatureSet/Dashboard/src/Locales/ja.json # packages/App/FeatureSet/Dashboard/src/Locales/ko.json # packages/App/FeatureSet/Dashboard/src/Locales/nl.json # packages/App/FeatureSet/Dashboard/src/Locales/no.json # packages/App/FeatureSet/Dashboard/src/Locales/pt.json # packages/App/FeatureSet/Dashboard/src/Locales/ru.json # packages/App/FeatureSet/Dashboard/src/Locales/sv.json # packages/App/FeatureSet/Dashboard/src/Locales/zh-CN.json # packages/App/FeatureSet/Dashboard/src/Locales/zh-TW.json # packages/E2E/package.json
… Fix without asking, older rules say what they do, one-click Runner rebind The rule dialog: - "Fix without asking" no longer says it "starts the runbooks you picked" when none are picked: "Runbooks start on their own only if you pick them under More fields." (a pick from any runbook is always proposed). - The Runner picker draws no line that repeats its choice when the project has Runners; with none, it says where to add one (Runbooks > Runners, in a new tab) - never to someone who could not read the Runners. - A rule saved before rules were simplified says what it still does at the top of its Fix step, hides the fields that do nothing for it (Approval and Runner for an AI-picks-a-runbook rule, which always asks; Runbooks for a commands rule), and offers "Fix like a new rule", which clears its two flags and asks first. The edit form now carries both flags (never drawn). The rules table: "Fixed by" (OneUptime AI on almost every row) and "Approval" become one Fix column - the approval pill, and a quiet wrapping line only for a rule that does not fix the way new rules do - so Status no longer slides under the sticky row actions at 1280px (the card is ~940px beside the side menu). Elsewhere: - AI settings: the fixing switch's note is one sentence, "Nothing runs without approval unless the AI agent's settings or a rule allow it." - Remediation card: the project's own runbook pick reads "OneUptime AI", not "Rule: OneUptime AI" (DEFAULT_RUNBOOK_PICK_NAME moved to AutoRemediationFix so the Dashboard can read it). - Kubernetes cluster AI > Agent: a Runner bound while its switch was off gets "Choose it again" (one click writes the same Runner back, with the server's own checks) instead of "clear the Runner and choose it again", which saved nothing in one dialog session; the server's next step says so. - "Fix" is translated in 15 locales (the dialog's step and the new column). - Docs (ai/ai-sre, 17 languages): one sentence on "Fix like a new rule". - The offline suite moves to port 4395 (4391 is the Profiles suite's too). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…le it is on
"More fields · Runbooks: 2, Run them every time, Runner" read as if the
switch were on: an unset field is named in a lighter shade, but its title
is a statement. A field may now say when a folded section's header names
it (Field.isListedWhileFolded, one check in getFoldedFormFieldItems); the
switch is named only while on ("Run them every time: On").
The older-rule note drops its unnamed group role: a sentence and a button.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ai-runbook-allowlist-ui-review Conflicts resolved: - Dashboard locales: a three-way merge by key (both sides' keys and translations kept, in master's order); i18n:check sound, the extractor finds nothing missing. - SchemaMigrations/Index.ts: master's 1804450000000 before this branch's 1804500000000 and 1804550000000. - E2E package.json: both suites' scripts. - test.app.yaml: master moved the Profiler and LLM Connect to a new group 7; Auto Remediation Rules joins them there, the lightest group. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…umn description ShortDescriptionsGuard read the two registrations the edit form now carries (aiComposesCommands, aiSelectsRunbook) as fields showing their model columns' API-reference descriptions (378 and 449 characters). They are never drawn: hideColumnDescription, which BasicModelForm and the guard both honour. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ai-runbook-allowlist-ui-review Brings in #5099 (separate Twilio numbers for calls and SMS). The migrations index conflicted: both sides appended. Resolved in timestamp order - this branch's 1804500000000 and 1804550000000, then master's AddTwilioSmsPhoneNumbers1804700000000 - and the next commit renumbers this branch's two past it. The 17 Dashboard locale files merged on their own and match a key-by-key three-way merge (31693 keys each, same order). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…io migration Master registered AddTwilioSmsPhoneNumbers1804700000000 while this branch was open. An installation that already ran it would run this branch's 1804500000000 and 1804550000000 after it, a fresh one before it, so they move past it: AddAutoRemediationRuleRunner1804750000000 and AddAutoRemediationRuleRunnerIndexAndKey1804800000000, registered last, with the tests and the upgrade docs suite that name them. Neither SQL changed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Why
What changed
An auto remediation rule's Fix step (Incidents → AI → Settings and Alerts → AI → Settings, under More settings → Auto remediation rules)
RunnerPicker+RunnerChoicesLoader). The first choice, and the default, is the AI agent on the affected cluster or host. The picker never offers a global Runner or the in-cluster Runner installed by the Kubernetes agent chart.Runners
RunnerAiCommandsPick.allowPickedRunners). It runs the same check the switch ran, under the project's credential-reach lock: a member who may not read runbook credentials cannot pick a Runner that holds SSH credentials. ThecanRunAiCommandscolumn stays, and only OneUptime writes it.RunnerChoicesLoader.Engine
AiPickingRunbookorAiRunbookPickNotStarted, classified as acted and needs attention.Decisions made on your behalf
GlobalRunnerMode.getJobRefusal); this PR does not widen that sandbox. So with no Runner picked, no Runner of your own is needed: OneUptime AI fixes through the AI agent on the affected cluster or host, as it does today. Runbooks OneUptime AI picks still run where each of their steps says, which is the Global Runner for a step that names none (feat(runners): global Runners, on by default like global probes; custom Runners for more security #5062). The rule's Runner applies only to OneUptime AI's own Bash and SSH commands; it never overrides a runbook step's Runner, since a step may need a particular host.1804750000000-AddAutoRemediationRuleRunnerand1804800000000-AddAutoRemediationRuleRunnerIndexAndKey, renumbered past master's Twilio migration in the UI review):AutoRemediationRule.runnerIdcolumn. Its index and its key toRunner(ON DELETE SET NULL) are built online.DROP_RUNBOOKS_THE_FORM_HID); the rule reads as any runbook, always proposed. Rules that run their runbooks every time, and the two older AI shapes, keep theirs.What changes for existing customers
canRunAiCommandsbecomes read-only: a write that names it is refused (Terraform's attribute becomes computed, as feat(runners): a code repository picks its Runner; Runners lose Runs AI Code Fixes #5086 did forcanRunCodeFixTasks).Left out
Tests, and what each proves
AutoRemediationFix(how every stored shape reads; allow-list ids; the Runner a rule's commands may use; when an AI pick may start),AutoRemediationDecision(the new reasons are acted / need attention).AutoRemediationDecisionRecording(+21: the no-rule pick for incidents and alerts, once per signal; a rule naming none always proposes; naming some and Fix without asking starts, unless another rule asks first, an AI agent round fixes the signal, or nothing is enabled; no LLM provider; a rule's Runner adds a command run; an every-time rule picks nothing),RemediationPlanRunner(+17: candidates are the rule's runbooks or every enabled one; the framing says proposed or started; a start re-reads the rule under the breaker lock; breaker, failed start and lost race propose or settle; a proposed pick or the project's pick never starts),RemediationCommandToolsandRemediationExecutionRunner(commands target only the Runners a rule names, never "any Runner").AutoRemediationRuleRunner(a rule's Runner: refused for an agent Runner, allowed by the pick, written as id or relation),AutoRemediationRuleUnattendedCommands(widening checks read the rule's Runner),RunnerAiCommandsPick(the pick turns the column on only for the project's own non-global, non-agent Runners, and refuses a Runner with SSH credentials to someone who may not read them, under the lock), the Kubernetes cluster binding suites (binding is the consent; the gap text),RunnerServiceGlobalRunner,OwnedScopeEveryKindWithOwners,GlobalRunnerChoiceGuard.AddAutoRemediationRuleRunnerMigration(the statements, their order and registration) andAddAutoRemediationRuleRunnerPostgres(on real rows: Runners written out and picked, agent rows by the server's own rule, every-time and older rules kept, hidden runbooks dropped only from OneUptime AI rules, no Runner's switch changed, the key validated online, rollback). Opt-in; the schema drift workflow runs it.AutoRemediationRulesTableandAutoRemediationRuleFields(for incidents and alerts: no Fix With; Approval up front; More fields folded with its line; Runbooks none = any; every-time switch; the Runner picker; what a save sends),RunnerPickerModel(the remediation picker's first choice and helper text),RemediationDecisionLines,RunnerKubernetesAgentForm(the one-page Runner form without the switch), the Kubernetes AI page suites.AutoRemediationRunnerUpgradeDocs(the upgrade note in 17 languages),AISettingsSwitchesFirst,RunnerUi.npm run test-auto-remediation-rules-ui(Dashboard Offline UI group 7 since the last master merge, about 2 minutes with its bundle): the real pages in Chromium - create and edit, More fields, the Runner choices, rules saved before, alert rules, a viewer, a phone, dark mode and German.GlobalRunnersand the deployedRunnersUnderRunbooksspec check the Create Runner dialog has no switch.Verified locally
After merging master at 387b345, with ee/ hidden as CI runs them and billing on:
HookWritesCallersMayMake: RunnerService's create hook wrotecanRunAiCommands, which no caller may write any more. The hook no longer writes it, and the column's default (off) applies.UpdateChecksReadHeldRows: the rule service read an update's rules by its query. It now reads the projects throughfindProjectsToCheckUpdateIn/getProjectsToCheckRowsIn. As a result, a master admin's update made in another project allows the Runner in the rule's own project.MarkdownEditorLongLines(master's fix(markdown-editor): the editor reads and writes a note in time in line with its length #5091), is a 2-second timing check that ran 2.4 s under two full suites at once. It passes alone.After merging master again at 3e2cf23 (#5097 nginx, then #5092 Profiles):
i18n:extractduring the merge, keeping both sides' keys and translations; the extract also re-adds 43 keys from ee/ and other PRs' models that no committed locale carries, so those were dropped again),test.app.yamlgroup 6 (both suites; see below), andpackages/E2E/package.json(both scripts).compile-testspasses. No package reads files outside itself and Common (tsc --listFiles).i18n:checkpasses, and the Ops suites that readtest.app.yaml(E2ESuitesRunInCiand three more) pass.Tests/Server/API/ExpiredSessionAIRoutes.test.ts. feat(profiles): the Profiler answers where the time goes and what changed; Function Share monitors, Fix with AI, profile evidence for AI #5092 added/ai-investigation/create-profile-fix-taskand/ai-investigation/profile-fix-suggestionwithout adding them to that suite's table. This branch has both files exactly as master does.docs:check-anchors,i18n:validateand the Dashboard'si18n:checkpass, and no anchors are left to localize. The ReDoS checker finds no new patterns. Prettier and eslint pass on every changed.tsand.tsxfile.RunnersUnderRunbooks.spec.ts, which needs a live stack. Its selectors drive the same Create Runner dialog as the offline GlobalRunners suite, which passes.UI review
A second agent reviewed every surface of this PR with fresh eyes, rendered for real (the offline fixture's production pages in Chromium with Inter, plus a scratch harness of its own for languages, data shapes and states), and polished what kept it from great.
Looked at: the rule's create and edit dialog on every step state (folded and open More fields, Ask / Fix without asking, runbooks picked, Run them every time on and off, the Runner menu, no Runners, Runners loading, Runners unreadable, long Runner and runbook names), the rules table with rules saved before (all four shapes) and long rule names, both lanes (incidents and alerts), at 1280px, at 1000px (the card's real width beside the Dashboard side menu at a 1280px window) and at 390px, light and dark, in English, German, Japanese, Russian and Persian; the AI settings switches; Create Runner, Runner Details and the Runners list (GlobalRunners suite); the remediation feed items as Slack (Block Kit section) and Microsoft Teams (Adaptive Card TextBlock) draw them, built with the product's own converters, at desktop and phone widths; the Remediation card's suggestion lines.
Changed, and why:
RunnerChoices.areProjectRunnersUnread).Field.isListedWhileFolded(Common, oneifingetFoldedFormFieldItems); it is named while on ("Run them every time: On").DEFAULT_RUNBOOK_PICK_NAMEmoved toAutoRemediationFixwithisProjectRunbookPick.)Left as they are, on purpose: the feed messages (bold headline plus one or two plain sentences, consistent with the other remediation items, and they read well in Slack and Teams at both widths); Create Runner's Description line ("Optional description for this agent" is the model column's, as on master); the Runner picker's folded value (it names the Runner field, not the Runner: the name is known only once the picker has loaded).
Tests added or changed:
AutoRemediationRulesTable.test.tsx(+the Fix column, the older-rule note and its switch, every field's visibility for each shape, the folded listing; 70 tests),AutoRemediationRuleFields.test.tsx(no helper with Runners, where to add one with none, not when unreadable, the note component), newRemediationSuggestionCardSource.test.tsx(the project's pick, a rule's, a deleted rule's;isProjectRunbookPick),FoldedFormFields.test.ts(isListedWhileFolded),KubernetesClusterAiAgentStatus.test.tsandKubernetesClusterAiPageServerParity.test.ts(the step text andchoose_runner_again),KubernetesClusterAiPage.test.tsx(+3: one click writes the same Runner, a refusal is shown, a reader is told who to ask),AISettingsSwitchesFirst.test.ts(the note), the offline Playwright suiteAutoRemediationRules.spec.ts(now 20 tests: the Fix column, the table fitting its card at the real 1280px width, the no-Runners hint, the folded line, both older shapes kept, switched with Fix like a new rule, and left alone by Cancel; about 10 seconds more).Verified after the UI review, on the branch merged with master twice:
test.app.yaml(master had moved the Profiler and LLM Connect to a new group 7 and this suite joined them, the lightest group).AddTwilioSmsPhoneNumbers1804700000000took a slot above this branch's two migrations, so they were renumbered past it:1804750000000-AddAutoRemediationRuleRunnerand1804800000000-AddAutoRemediationRuleRunnerIndexAndKey, registered last, SQL unchanged. Every migration then ran on a fresh Postgres 15 in that order, the schema drift check found nothing, and the opt-inAddAutoRemediationRuleRunnerPostgressuite passed on the migrated database (21). The 17 Dashboard locale files merged on their own; they match a key-by-key three-way merge (31693 keys each, same order).Checks run:
tscandcompile-tests), the Dashboard, App with its tests, and E2E;--listFilesfor Common, App and E2E lists nothing from another package or the repository's Scripts.HookWritesCallersMayMake,UpdateChecksReadHeldRows,SchemaMigrationsOrdering, master'sLongFormStepsGuardandSmallWizardsOnePageGuard, Common's and App's locale suites, every App guard (41;ShortDescriptionsGuardcaught the two never-drawn flag fields showing their columns' API descriptions - fixed withhideColumnDescription; after the second merge it fails only on master's Twilio copy, below), App's root-level tests,AutoRemediationRunnerUpgradeDocsandCodeRepositoryRunnerUpgradeDocs.Docs*suites).i18n:check(sound, no translation lost; the extractor finds nothing missing),i18n:validate,docs:check-anchors, the Ops suites that readtest.app.yaml(E2ESuitesRunInCiand three more), and the ReDoS checker (no new patterns)..ts/.tsxfile of the branch.Not from this PR:
ShortDescriptionsGuardfails on master since #5099 - the Twilio SMS numbers description is 192 characters (limit 150) in the Dashboard's Twilio config table and the Admin Dashboard's Call and SMS settings. This branch does not touch either, so it fails the same way here; the fix belongs with the Twilio feature.Not run: the deployed
RunnersUnderRunbooks.spec.tsagainst a live stack (its selectors match the offline suite, which passes), a live LLM and Runner, the Kubernetes AI page in a browser (its new button is the same Button, placement and flow as the existing Turn on in the same list, covered by three jsdom tests).🤖 Generated with Claude Code