fix(llm-agents): delete the flows three agent specs left behind (#1346) - #1348
Merged
Conversation
`agent-max-iterations`, `agent-empty-refusal-response` and `agent-model-connection-isolation` load the Simple Agent template and never delete the flow they run on. That costs twice: an orphan `Simple Agent` per test on the shared instance, and — because token attribution lives on the delete path (#1197) — the tokens those runs spend reach the QA platform with no spec to claim them. Measured on the 2026-08-06 daily (run 31093877484): the run's `unattributed` bucket is 4,120 tokens over 4 `claude-haiku-4-5` calls, and all of it comes from three traces owned by two of these specs — `1027dfd2` (936) and `6676e05d` (918) from `agent-empty-refusal-response`, `e7c60610` (2,266, 2 calls) from `agent-max-iterations`. None of the three flow ids appears in any `token-attrib-*.jsonl`, so no delete ever ran for them. The `attrib_cost` records these specs did produce came from `loadTemplateByName`'s cleanup of the surplus flows it creates: those never ran, carried no traces, and the attribution read came back empty. Wire each spec to the shared tracker (#1108) — capture the creation POST, delete id-scoped in `afterEach`, never a delete-all sweep (#553). `cleanup()` derives the attribution from the running test on its own (#1197 §1.1) and stays inert unless the lane sets `TOKENS_ATTRIB`, so local runs and the PR lane are unaffected. Also correct the same stale claim in all three specs and their docs: `SimpleAgentTemplatePage.load()` does not wipe existing flows — the cross-worker delete-all was removed in #553 — which is why the missing cleanup read as intentional. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…#1264 The PR lane went red on `agent-max-iterations` › "agent stops when max iterations is reached", 3 of 3 attempts, with the agent answering the task normally instead of returning `Model call limits exceeded: run limit (1/1)`. The bubble renders and the locator resolves 34 times, so it is a content failure, not a timeout — the signature #1264 has been open on since the 2026-08-04 daily. Not this branch's diff: the same test fails identically on `origin/main`. What changed is the SELECTION — touching this file put a spec the daily never runs (`@regression`, never `@stable`) into the impacted-specs lane. The file is serial, so that failure also skipped the `@stable` causal control — the half that works, and the half this branch's cleanup change needed to exercise. So a red here bought no coverage and cost the lane its verification. Quarantine with `test.fixme`, the same treatment mcp-server.spec.ts (#1266) and openai-compatible-provider-setup.spec.ts already carry, and record the measurement where a reader will hit it: reproduced on 1.12.0.dev18 LOCALLY, off CI load, on claude-haiku-4-5, claude-opus-5 and claude-opus-4-5 — which rules out the mid-run backend wedge #1264's triage left open as a possible cover. The causal control is deliberately NOT quarantined: on its own it proves only that a high limit finishes, and it is what will show the pair working again. Lifting the quarantine is #1264's call. Checklist bullets move from `[x]` to `[~]` with the reason, in both §6.2 and §7.7. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
rafaelgiln
added a commit
that referenced
this pull request
Aug 7, 2026
…1355) (#1357) * fix(collect-models): wait for Save to leave aria-busy, and stop swallowing a save that never landed (#1355) `Collect models` failed twice in a row on PR #1348 with locator.click: Timeout 20000ms exceeded - waiting for getByRole('button', { name: 'Save', exact: true }) - locator resolved to <button aria-busy="true" aria-disabled="true" …> The button was not broken: the panel is walked one provider at a time and the PREVIOUS provider's validation was still in flight. `click()` does wait for "enabled and stable", but its ceiling is 20s — shorter than the ~35s Google validation this file already documents — and when it expires the error names the click, sending the reader to the wrong step entirely. It surfaced when the CI `OPENAI_API_KEY` was replaced with a funded key: saving openai used to fail fast on `no credits remaining`, so no validation was ever in flight when the loop moved on. Three changes, one per defect: - `waitForButtonIdle` polls the button until it is genuinely actionable — `aria-busy`, `aria-disabled` and `isEnabled()` are three separate claims and any one of them blocks a click — with a 60s ceiling sized against the validation, and `formatSaveBusyFailure` names the provider, the observed attributes and the likely cause. It returns a verdict rather than throwing, so the caller supplies the provider name and the whole decision is unit-testable without a browser. - The `waitFor(Disconnect, 60s)` after the save no longer ends in `.catch(() => {})`. "Never configured" and "configured fine" were the same observation, which is why an empty model list downstream read as a provider without models. - A provider whose key IS set collecting ZERO models now warns. That state is what produces the silent daily: `Collect models` is `continue-on-error` there (#980), so a `models.json` missing a provider makes every parametrized spec skip and the run reports green having tested no agent — #570/#1012's green-by-absence. Six unit tests drive the wait with an injected clock and a scripted locator: the real failure needs a funded key and a slow backend, but the decision — when it gives up and what it reports — is testable without either. They pin both orderings of the not-idle state, and that a zero timeout still observes once rather than reporting a state it never read. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(collect-models): confirm each model toggle, so one provider's writes stop wedging the next (#1355) The instrumentation from the previous commit named the real cause, and it is not the one that commit assumed. The Save button was not merely slow to settle:⚠️ provider "anthropic" never showed the configured state ("Disconnect") within 60s of Save⚠️ provider "anthropic" has a key configured but collected ZERO models Models found (anthropic): [] Error: the "Save" button for provider "google" never became actionable after 60.0s anthropic's Save was clicked on an IDLE button and never completed. Waiting longer was never going to help — 120s across the two providers and nothing settled. The failure screenshot shows why: OpenAI carries a `52 models` badge while google's Save spins without anyone having clicked it, so the busy state is the form's, not the button's, and the write in flight is anthropic's. What puts it there: enabling a model is a WRITE, and this loop enables every model of every provider as fast as the clicks land. With a funded OpenAI key the panel exposes 41 visible models where a drained key exposed none worth toggling — so one provider went from ~0 writes to 41 against a backend the lanes run with `LANGFLOW_WORKERS=1`. The next provider's Save queues behind them. `waitForToggleChecked` confirms `aria-checked="true"` after each click, which is what serialises the writes. Two bounds, mirroring the token-attribution sidecar (#1197 §4.4): 5s per toggle bounds ONE write, and a 60s per-provider budget bounds the sum — a per-item timeout alone would let 41 slow-but-succeeding confirmations spend 41 x 5s and blow the spec's own 5-minute budget. Past the budget the clicks continue unconfirmed and the count is warned about, never silently dropped (#1012). Four more unit tests, same injected-clock approach; deleting the confirmation kills three of them. Locally the healthy path is unchanged — 23.9s against 24.9s before, 3 providers, 90 models, no new warning fired — because a confirmation on a responsive panel costs tens of milliseconds. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(collect-models): wait for the credential WRITE, not for a clock (#1355) Reproduced locally with a funded OpenAI key and every `/api/v1/variables/` response logged. The measurement settles it: collect-models: provider "anthropic" credential write took 103.1s (HTTP 201) All three saves answer 201 — none is rejected, none is refused. anthropic's POST simply does not come back for ~103s, and the panel keeps its Save `aria-busy` for as long as a write is in flight. So the previous two commits were both measuring the same thing from the outside and calling it different names: the 60s button wait, and the toggle serialisation, were guesses at a duration. Waiting for the RESPONSE removes the guess. `page.waitForResponse` is registered before the click (or the response can land first and be missed) and resolves the moment the write answers, with a 180s backstop set well clear of the worst measurement rather than just above it — the cost grows with how many providers are already configured, which is why any fixed clock here eventually expires again. The outcome is reported three ways, none of them silent: no write observed, a write that answered non-2xx, and a write that succeeded but took over 10s — the last one is a trend line, so the next time this cost approaches the ceiling it is visible before it crosses it. Verified on a fresh container with `--retries=0`: `1 passed (2.6m)`, all three providers collected, where the same command previously left anthropic at zero models and died on google's Save. The toggle serialisation from the previous commit stays, with its comment corrected to say what it is: it was added on a hypothesis the next CI run refuted, it fixed nothing, and it is kept only because a burst of 41 unconfirmed writes against a LANGFLOW_WORKERS=1 backend is worth avoiding at a measured cost of ~0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Rafael <rafael@oriontech.me> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Victor-w-Madeira
deleted the
fix/issue-1346-attribute-leaked-agent-flows
branch
August 8, 2026 07:03
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.
Closes #1346.
What the issue asked
Name which of the four candidate paths puts a run's tokens in the platform's
unattributedbucket. On run 31093877484 (2026-08-06) that bucket was 4,120 tokens / 4 calls / $0.0052 — 8.0% of the run.The answer: path 4 — the flow is never deleted at all
Reconstructed trace by trace from the run's own artifacts (
token-probes-*.jsonl×token-attrib-*.jsonl×results.json):1027dfd2abb0557aagent-empty-refusal-response.spec.ts— model refusal…6676e05dc52cca3eagent-empty-refusal-response.spec.ts— empty response…e7c60610b5a8c06aagent-max-iterations.spec.ts— causal control… (@stable)openai-provider+traces-list-filters936 + 918 + 2266 = 4,120; 1 + 1 + 2 = 4 calls. The three zero-token traces cost nothing.
Both specs call
SimpleAgentTemplatePage.load()and never delete the flow it returns. Token attribution lives on the delete path (#1197), so a flow that is never deleted is never attributed.What made this hard to read: both specs do emit an
attrib_costrecord per test. That record comes fromloadTemplateByName's own cleanup of the surplus flows it creates — those never ran, carried no traces, and the attribution read came back empty.The other three paths are refuted by the same data, not by argument:
token-attrib-*.jsonl, so no delete ran for them at all.test.info()to name) — theattrib_costrecords name both the test and the file, so resolution worked.test end − trace startgap for all 29 attributed token-bearing traces on this run: 2.5 s to 13.1 s. The unattributed ones sit at 1.9 / 2.4 / 4.6 s, inside that range. The gap does not discriminate; the missing delete does.Contra-example on the same run:
agent-multi-tool-selection.spec.ts— same template, same model, has an id-scopedafterEach, attributed 2 of 2 traces.The change
A repo-wide scan found exactly three specs that load a template and clean up nothing. All three are wired to the shared tracker (#1108): capture the creation POST, delete id-scoped in
afterEach, never a delete-all sweep (#553).cleanup()derives the attribution from the running test on its own (#1197 §1.1) and stays inert unless the lane setsTOKENS_ATTRIB, so local runs and the PR lane are unaffected.agent-max-iterations.spec.tsagent-empty-refusal-response.spec.tsagent-model-connection-isolation.spec.ts— never executes its flow, so it produced no unattributed tokens; the leak was its whole cost, and the same hook closes both.The same stale claim is corrected in all three specs and their docs:
SimpleAgentTemplatePage.load()does not wipe existing flows — the cross-worker delete-all was removed in #553 — which is why the missing cleanup read as intentional.Verification — local A/B
start-langflow-docker.shsetsLANGFLOW_DEACTIVATE_TRACING=trueby design, so a second nightly container was started with tracing on to measure the attribution half.TRACElines inTOKENS_ATTRIBorigin/main)The three lines, attributed to the right spec and test, reproducing the daily almost token for token:
npm run typecheckclean,npm run lint0 errors, both QA-CHECKLIST guards pass.Reproduce:
Expected: 4 passed, 3
TRACElines, flow count unchanged.The issue's own cheap check still applies as the final proof: the
unattributedrow on the platform'sBy spectable should fall to the zero-token traces on the next daily.Not in scope, observed while verifying
agent-max-iterations› "agent stops when max iterations is reached" fails onclaude-haiku-4-5(and onclaude-opus-5/claude-opus-4-5in a multi-model sweep): the agent replies"I'll fetch that URL for you."with no limit message. It fails identically onorigin/main, so it is not this diff. Already tracked as #1264 — this run adds that it reproduces locally, off CI load, across several Anthropic models. That test is@regression, never@stable, which is why the daily never surfaces it.🤖 Generated with Claude Code