This is an automated email from the ASF dual-hosted git repository.

github-merge-queue[bot] pushed a commit to branch 
gh-readonly-queue/main/pr-7821-957b6c965640c07ace034eb9edcae53ef974324a
in repository https://gitbox.apache.org/repos/asf/texera.git

commit 28594efe6cf7628be81bd9ff8939199417773ecd
Author: roshiiiiz <[email protected]>
AuthorDate: Sun Sep 20 00:43:43 2026 +0000

    fix(agent-service): reject maxSteps 0 in settings (#7821)
    
    <!--
    Thanks for sending a pull request (PR)! Here are some tips for you:
    1. If this is your first time, please read our contributor guidelines:
    [Contributing to
    Texera](https://github.com/apache/texera/blob/main/CONTRIBUTING.md)
      2. Ensure you have added or run the appropriate tests for your PR
      3. If the PR is work in progress, mark it a draft on GitHub.
      4. Please write your PR title to summarize what this PR proposes, we
        are following Conventional Commits style for PR titles as well:
    - `fix` is for behavior that worked before and no longer does; adding or
    removing a functionality, or reworking one so that user-facing behavior
    intentionally changes, is a `feat`; a change that leaves the user-facing
            behavior unchanged is a `refactor`.
    - A test-only PR is `test(<module>): ...`; repairing a broken test is
            `fix(test, <module>): ...`.
    - A dependency bump is `fix(deps, <module>): ...` when it patches a CVE
    and `chore(deps, <module>): ...` otherwise; GitHub Actions bumps take
            `ci` as their module, e.g. `chore(deps, ci): ...`.
    - A PR targeting a release branch appends the version as the last scope
            component, e.g. `fix(deps, frontend, v1.2): ...`.
        See CONTRIBUTING.md for the full convention.
      5. Be sure to keep the PR description updated to reflect all changes.
    -->
    
    ### What changes were proposed in this PR?
    <!--
    Please clarify what changes you are proposing. The purpose of this
    section
    is to outline the changes. Here are some tips for you:
      1. If you propose a new API, clarify the use case for a new API.
      2. If you fix a bug, you can clarify why it is a bug.
      3. If it is a refactoring, clarify what has been changed.
      3. It would be helpful to include a before-and-after comparison using
         screenshots or GIFs.
      4. Please consider writing useful notes for better and faster reviews.
    -->
    This PR fixes a critical bug where providing a `maxSteps` value of `0`
    or negative would cause the agent execution loop to hang indefinitely.
    
    Changes made:
    - Updated the Elysia validation schema in `server.ts` to enforce a `{
    minimum: 1 }` rule for `maxSteps`. This guarantees the server rejects
    invalid limits at the API boundary with a 400 Bad Request.
    - Added a fallback boundary clamp using `Math.max(1, updates.maxSteps)`
    in `texera-agent.ts` during agent settings updates. This guarantees
    internal state strictly enforces the limit even if schema validation is
    bypassed.
    - Added a new automated unit test to `texera-agent.spec.ts` asserting
    that `maxSteps` is correctly clamped to 1 if `0` or a negative number is
    provided.
    
    ### Any related issues, documentation, discussions?
    <!--
    Please use this section to link other resources if not mentioned
    already.
    1. If this PR fixes an issue, please include `Fixes #1234`, `Resolves
    #1234`
    or `Closes #1234`. If it is only related, simply mention the issue
    number.
      2. If there is design documentation, please add the link.
      3. If there is a discussion in the mailing list, please add the link.
    -->
    Closes #7484
    
    ### How was this PR tested?
    <!--
    If tests were added, say they were added here. Or simply mention that if
    the PR
    is tested with existing test cases. Make sure to include/update test
    cases that
    check the changes thoroughly including negative and positive cases if
    possible.
    If it was tested in a way different from regular unit tests, please
    clarify how
    you tested step by step, ideally copy and paste-able, so that other
    reviewers can
    test and check, and descendants can verify in the future. If tests were
    not added,
    please describe why they were not added and/or why it was difficult to
    add.
    -->
    Tested manually via local API and verified with automated unit tests:
    1. Booted the local `agent-service` via `bun run dev`.
    2. Issued a `PATCH /api/agents/:id/settings` HTTP request passing
    `{"maxSteps": 0}`.
    3. Verified the server correctly rejected the payload with a `400 Bad
    Request` schema validation error ("Expected number to be greater or
    equal to 1").
    4. Ran the automated `agent-service` test suite (`bun test`) which
    executes the new dedicated unit test asserting the clamping behavior.
    The entire suite successfully passed.
    
    ### Was this PR authored or co-authored using generative AI tooling?
    <!--
    If generative AI tooling has been used in the process of authoring this
    PR,
    please include the phrase: 'Generated-by: ' followed by the name of the
    tool
    and its version. If no, write 'No'.
    Please refer to the [ASF Generative Tooling
    Guidance](https://www.apache.org/legal/generative-tooling.html) for
    details.
    -->
    Generated-by: Antigravity (DeepMind)
    
    
    <img width="897" height="301" alt="image"
    
src="https://github.com/user-attachments/assets/3c6b76c9-0fd2-4609-9a9b-1187e7f19ca0";
    />
    
    
=========================================================================================================================================================================================================================================================
    <img width="917" height="402" alt="image"
    
src="https://github.com/user-attachments/assets/7a01398e-0db2-4088-a32f-9f810458d692";
    />
    
    ---------
    
    Co-authored-by: Xuan Gu <[email protected]>
---
 agent-service/src/agent/texera-agent.spec.ts | 11 +++++++++++
 agent-service/src/agent/texera-agent.ts      |  2 +-
 agent-service/src/server.ts                  |  4 ++--
 3 files changed, 14 insertions(+), 3 deletions(-)

diff --git a/agent-service/src/agent/texera-agent.spec.ts 
b/agent-service/src/agent/texera-agent.spec.ts
index 7c99ffaa3e..d173fe7ae0 100644
--- a/agent-service/src/agent/texera-agent.spec.ts
+++ b/agent-service/src/agent/texera-agent.spec.ts
@@ -223,6 +223,17 @@ describe("TexeraAgent", () => {
       
expect(after.maxOperatorResultCharLimit).toBe(before.maxOperatorResultCharLimit);
     });
 
+    test("clamps maxSteps to a minimum of 1 and truncates fractions", () => {
+      agent.updateSettings({ maxSteps: 0 });
+      expect(agent.getSettings().maxSteps).toBe(1);
+
+      agent.updateSettings({ maxSteps: -5 });
+      expect(agent.getSettings().maxSteps).toBe(1);
+
+      agent.updateSettings({ maxSteps: 2.5 });
+      expect(agent.getSettings().maxSteps).toBe(2);
+    });
+
     test("returns a new settings object (mutating primitive fields does not 
stick)", () => {
       agent.getSettings().maxSteps = 12345;
 
diff --git a/agent-service/src/agent/texera-agent.ts 
b/agent-service/src/agent/texera-agent.ts
index 34b5f32f98..b98c812b0b 100644
--- a/agent-service/src/agent/texera-agent.ts
+++ b/agent-service/src/agent/texera-agent.ts
@@ -379,7 +379,7 @@ export class TexeraAgent {
       this.settings.disabledTools = updates.disabledTools;
     }
     if (updates.maxSteps !== undefined) {
-      this.settings.maxSteps = updates.maxSteps;
+      this.settings.maxSteps = Math.max(1, Math.trunc(updates.maxSteps));
     }
     if (updates.allowedOperatorTypes !== undefined) {
       this.settings.allowedOperatorTypes = updates.allowedOperatorTypes;
diff --git a/agent-service/src/server.ts b/agent-service/src/server.ts
index 42247399e8..ae358293fc 100644
--- a/agent-service/src/server.ts
+++ b/agent-service/src/server.ts
@@ -242,7 +242,7 @@ const agentsRouter = new Elysia({ prefix: "/agents" })
             toolTimeoutSeconds: t.Optional(t.Number()),
             executionTimeoutMinutes: t.Optional(t.Number()),
             disabledTools: t.Optional(t.Array(t.String())),
-            maxSteps: t.Optional(t.Number()),
+            maxSteps: t.Optional(t.Integer({ minimum: 1 })),
             allowedOperatorTypes: t.Optional(t.Array(t.String())),
           })
         ),
@@ -382,7 +382,7 @@ const agentsRouter = new Elysia({ prefix: "/agents" })
         operatorResultSerializationMode: t.Optional(t.Literal("tsv")),
         toolTimeoutSeconds: t.Optional(t.Number()),
         executionTimeoutMinutes: t.Optional(t.Number()),
-        maxSteps: t.Optional(t.Number()),
+        maxSteps: t.Optional(t.Integer({ minimum: 1 })),
         disabledTools: t.Optional(t.Array(t.String())),
         allowedOperatorTypes: t.Optional(t.Array(t.String())),
       }),

Reply via email to