fix(automations): raise iteration cap to 50, surface update errors#369
Merged
Conversation
The create-form default of 5 iterations amputated analytical automations mid-run: they hit the cap, produced no final text, and reported "No output captured for this run." The iteration cap is a runaway backstop, not a cost budget — per-run cost is bounded directly by maxInputTokens and maxRunDurationMs — so a low cap is nearly all downside. - Raise the automation maxIterations cap 15 -> 50 (the engine's absolute MAX_ITERATIONS ceiling) in validateAutomationFields, the schema description, and the type doc comment. - Raise the create-form default 5 -> 25 (DEFAULT_MAX_ITERATIONS) and the input max 15 -> 50. - Surface update failures in the UI: handleUpdate no longer swallows the rejection in a silent catch, and the detail view's saveField renders the error in its existing error banner. Previously a rejected edit (e.g. an out-of-range maxIterations) looked like a silent no-op — the field just snapped back to its old value. - Update server.test.ts to pin the new 1..50 bound.
…constant Import MAX_ITERATIONS from src/limits.ts for the bundle's upper-bound check instead of a literal 50, so the validation bound provably tracks the engine's enforcement (engine.ts: Math.min(config.maxIterations, MAX_ITERATIONS)). A drifted literal would otherwise let the bundle accept values the engine silently clamps, or reject values it would honor. The schema description stays a literal: src/tools/platform/schemas/ is codegen'd under a strict rootDir (scripts/tsconfig.codegen-web.json) that forbids importing from outside that dir, so the constant can't reach it. A comment points at the source of truth; the description is documentation only.
mgoldsborough
added a commit
that referenced
this pull request
Jun 3, 2026
…ilure (#371) The error-surfacing fix from #369 was defeated by its own refresh call. saveField set the error in its catch, then unconditionally called loadDetail(), whose first synchronous statement is setError(null) (before its await). Both updates landed in the same microtask continuation, React batched them, null won, and the banner never rendered — so a rejected save (e.g. an out-of-range maxIterations) still looked like a silent no-op, the exact symptom #369 set out to fix. Refresh only on success. On failure the error persists and the closed inline editor re-renders the field at its unchanged saved value. No regression test: the bundle UIs have no component-test harness (@testing-library), so this React state-timing path can't be covered without standing up RTL. Verified manually against the batching trace. Co-authored-by: Mathew Goldsborough <1759329+mgoldsborough@users.noreply.github.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.
Problem
A scheduled automation ("daily users" on a tenant) reported
No output captured for this runwithStop: max_iterations. These are one phenomenon: the agent called a tool on every iteration up to the cap — including the final wrap-up step — so the loop exited via the iteration ceiling instead of the model saying "done." The run'soutputonly accumulates assistant text blocks, so a run that ends mid-tool-call has empty output → the reader's "No output captured" fallback.Two root contributors:
nb__search→ promote → call) before real work starts, so 5 leaves almost nothing for the actual task + synthesis. The run we debugged spent 2 of 5 steps onmanage_workspaces/manage_toolsdiscovery alone.maxIterationsto a value above the cap was rejected server-side (maxIterations must be between 1 and 15), buthandleUpdatecaught it in a silentcatch {}and refetched — so the field snapped back and it looked like "save isn't saving."Changes
MAX_ITERATIONS) invalidateAutomationFields, the platform schema description, and the type doc comment.DEFAULT_MAX_ITERATIONS) and inputmax15 → 50.handleUpdatelets the rejection propagate (still refreshes infinally);saveFieldrenders it in the detail view's existing error banner.server.test.tsnow pins the1..50bound (rejects 51, accepts 50 and 25).Rationale
The iteration cap is a runaway-loop backstop, not a cost budget — per-run cost is bounded directly by
maxInputTokens(200k) andmaxRunDurationMs(120s). A run that hits the cap has already spent the tokens and yields nothing, so a low cap converts "expensive but useful" into "expensive and useless." 50 matches the engine ceiling and the chat surface.Verify
bun run verifygreen (static + unit + web + integration + smoke);build:bundlesrebuilds the automations UI clean.