Skip to content

fix: use provided value in fillForm instead of LLM-hallucinated arguments#1805

Closed
elliotllliu wants to merge 2 commits intobrowserbase:mainfrom
elliotllliu:fix/fillform-use-value-param
Closed

fix: use provided value in fillForm instead of LLM-hallucinated arguments#1805
elliotllliu wants to merge 2 commits intobrowserbase:mainfrom
elliotllliu:fix/fillform-use-value-param

Conversation

@elliotllliu
Copy link

@elliotllliu elliotllliu commented Mar 10, 2026

Summary

Fixes #1789 — The fillForm tool now uses the actual value parameter instead of LLM-hallucinated placeholder values.

Problem

The fillForm tool receives { action, value } for each field, but only passes action to observe(). The LLM then hallucinates placeholder values like test@example.com in the observe results, which get typed into form fields instead of the real values.

// Agent correctly parses: value = "tiwa@trysplendor.com"
// But observe() returns:  arguments = ["test@example.com"]  ← hallucinated

This breaks login forms, 2FA flows, search queries, and any workflow where specific values matter.

Fix

After observe() returns results, override the arguments array with the actual value from the fields parameter when method === "fill":

if (res.method === "fill" && fields[i]?.value) {
  res.arguments = [fields[i].value];
}

Changes

File Change
packages/core/lib/v3/agent/tools/fillform.ts Override observe result arguments with caller-provided values (+10/-1)

Summary by cubic

fillForm now uses the caller-provided value for each field instead of hallucinated placeholders from observe(). Preserves empty-string inputs and prevents wrong values in login, 2FA, and search. Fixes #1789.

  • Bug Fixes
    • When method is "fill", override observe() arguments with fields[i].value using != null checks.
    • Apply index-based overrides only when observeResults.length === fields.length to avoid misassignment.

Written for commit 7db7b4f. Summary will update on new commits. Review in cubic

…ents

The fillForm tool receives a value parameter for each field but never
uses it. When observe() returns results, the LLM hallucinates placeholder
values (e.g. "test@example.com") in the arguments array. This causes
wrong values to be typed into form fields.

Override the observe result arguments with the actual value from the
fields array when the method is "fill", ensuring user-provided values
are used for form input.

Fixes #1789
@changeset-bot
Copy link

changeset-bot bot commented Mar 10, 2026

⚠️ No Changeset found

Latest commit: 7db7b4f

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 10, 2026

Greptile Summary

This PR fixes a real bug in the fillForm tool where observe() was returning LLM-hallucinated placeholder values (e.g., test@example.com) instead of the actual values provided by the caller, breaking login forms, 2FA flows, and any workflow requiring specific input.

Key changes:

  • The for...of loop over observeResults is replaced with an indexed for loop so that observeResults[i] can be paired with fields[i].
  • When res.method === "fill" and fields[i]?.value is truthy, res.arguments is overridden with the caller-supplied value before v3.act() is called.

Issues found:

  • The index-based pairing of observeResults[i] to fields[i] assumes a 1:1, same-order correspondence. If observe() returns interleaved non-fill steps (e.g., a "click" to focus a field), the indices shift and the wrong value is typed into the wrong field. A fill-counter approach would be more robust.
  • fields[i]?.value is falsy for empty strings, so an intentional value: "" (e.g., clearing a field) would silently fall back to the LLM's hallucinated argument; the guard should use !== undefined instead.

Confidence Score: 3/5

  • The fix addresses a real bug but introduces a fragile index alignment assumption that can cause wrong values to be typed into the wrong fields when observe returns non-fill intermediate steps.
  • The core idea is correct and the common-case behavior is improved. However, the index-based pairing between observeResults and fields is not guaranteed, and the falsy check for empty strings is an edge-case bug. Both issues can cause incorrect form values to be submitted.
  • packages/core/lib/v3/agent/tools/fillform.ts — the index alignment logic needs review before merging.

Important Files Changed

Filename Overview
packages/core/lib/v3/agent/tools/fillform.ts Adds index-based override of LLM-hallucinated fill arguments with caller-provided values; fix is correct for the common case but the index alignment between observeResults and fields is fragile and can break when observe returns interleaved non-fill actions.

Sequence Diagram

sequenceDiagram
    participant Caller as Agent/Caller
    participant FillForm as fillFormTool
    participant Observe as v3.observe()
    participant Act as v3.act()

    Caller->>FillForm: fields=[{action, value}, ...]
    FillForm->>Observe: instruction (actions only, no values)
    Observe-->>FillForm: observeResults[i] (may contain hallucinated arguments)
    loop for each observeResults[i]
        alt res.method === "fill" && fields[i]?.value
            FillForm->>FillForm: Override res.arguments with fields[i].value
        end
        FillForm->>Act: v3.act(res, actOptions)
        Act-->>FillForm: actResult
    end
    FillForm-->>Caller: { success, actions, playwrightArguments }
Loading

Last reviewed commit: e4e65ec

Comment on lines +59 to +67
for (let i = 0; i < observeResults.length; i++) {
const res = observeResults[i];

// Override LLM-hallucinated arguments with the actual value
// provided by the caller to prevent placeholder values like
// "test@example.com" from being typed instead of real input.
if (res.method === "fill" && fields[i]?.value) {
res.arguments = [fields[i].value];
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Index alignment between observeResults and fields is not guaranteed

The fix pairs observeResults[i] with fields[i] by position, but observe() may not always return exactly one result per field in the same order. If the LLM inserts intermediate steps (e.g., a "click" to focus a field before a "fill"), the indices shift and the wrong value gets applied to the wrong field.

Example of a broken case:

fields = [
  { action: "type email", value: "user@example.com" },
  { action: "type password", value: "secret123" },
]

observeResults = [
  { method: "click",  ... },          // i=0 → skipped (correct)
  { method: "fill",   ... email },    // i=1 → fields[1].value = "secret123" ← WRONG
  { method: "fill",   ... password }, // i=2 → fields[2]?.value = undefined  ← no override
]

A more robust approach would be to count only the fill results seen so far to determine which fields entry to use:

let fillIndex = 0;
for (let i = 0; i < observeResults.length; i++) {
  const res = observeResults[i];

  // Override LLM-hallucinated arguments with the actual value
  // provided by the caller to prevent placeholder values like
  // "test@example.com" from being typed instead of real input.
  if (res.method === "fill") {
    if (fields[fillIndex]?.value) {
      res.arguments = [fields[fillIndex].value];
    }
    fillIndex++;
  }

  // ... rest of loop
}

This way, the first fill result always maps to fields[0], the second to fields[1], etc., regardless of any interleaved non-fill actions.

// Override LLM-hallucinated arguments with the actual value
// provided by the caller to prevent placeholder values like
// "test@example.com" from being typed instead of real input.
if (res.method === "fill" && fields[i]?.value) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty string values are silently ignored

The condition fields[i]?.value is falsy when value is an empty string "". This means a caller that explicitly wants to clear a field (e.g., { action: "clear the search box", value: "" }) would fall back to the LLM's hallucinated argument instead of using the intended empty string.

Use a more explicit check:

Suggested change
if (res.method === "fill" && fields[i]?.value) {
if (res.method === "fill" && fields[i]?.value !== undefined) {

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 1 file

Confidence score: 3/5

  • Potential user-impacting bug in packages/core/lib/v3/agent/tools/fillform.ts: index-based mapping between fields[i].value and observeResults[i] can misassign values if observe output order/count differs.
  • Valid empty-string inputs are dropped in packages/core/lib/v3/agent/tools/fillform.ts due to a truthy guard, which could prevent intended overrides; this drives the moderate risk score.
  • Pay close attention to packages/core/lib/v3/agent/tools/fillform.ts - ensure field/value mapping and empty-string handling are correct.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/core/lib/v3/agent/tools/fillform.ts">

<violation number="1" location="packages/core/lib/v3/agent/tools/fillform.ts:65">
P2: Empty-string field values are ignored due to a truthy guard, so valid caller input (`""`) does not override observed fill arguments.</violation>

<violation number="2" location="packages/core/lib/v3/agent/tools/fillform.ts:65">
P1: Index-based mapping of `fields[i].value` to `observeResults[i]` can misassign form values when observe output order/count differs.</violation>
</file>

Since this is your first cubic review, here's how it works:

  • cubic automatically reviews your code and comments on bugs and improvements
  • Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
  • Add one-off context when rerunning by tagging @cubic-dev-ai with guidance or docs links (including llms.txt)
  • Ask questions if you need clarification on any suggestion

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

- Replace truthy check (fields[i]?.value) with != null so that
  empty-string values are preserved as valid form input
- Only apply index-based value overrides when observeResults.length
  matches fields.length to prevent misassignment when counts diverge
@elliotllliu
Copy link
Author

Thanks for the review @cubic-dev-ai — both issues were valid. Fixed in the latest push:

  1. Empty-string guard (P2) — Changed fields[i]?.value (truthy) → fields[i]?.value != null so that empty strings ("") are preserved as valid form input.

  2. Index mapping (P1) — Added a canMapByIndex guard: value overrides are only applied when observeResults.length === fields.length. When counts diverge, we skip the override and let the LLM's original arguments pass through, which is safer than misassigning values.

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.

fillForm agent tool drops value parameter, causing LLM to hallucinate fill values

1 participant