Skip to content

fix(workflows): serve canonical builtin service URLs, never a stored Docker-internal host - #4982

Merged
mmabrouk merged 1 commit into
big-agentsfrom
fix/builtin-invoke-url
Jun 30, 2026
Merged

fix(workflows): serve canonical builtin service URLs, never a stored Docker-internal host#4982
mmabrouk merged 1 commit into
big-agentsfrom
fix/builtin-invoke-url

Conversation

@mmabrouk

Copy link
Copy Markdown
Member

Symptom

A builtin agent app could not be invoked from the playground. The browser tried to
POST to a Docker-internal host and failed before reaching the service:

POST http://agenta-agent:8000/v0/invoke/inspect  ->  net::ERR_NAME_NOT_RESOLVED
The agent run failed — Failed to fetch

Root cause

Builtin app/evaluator service URLs are deterministic from the URI
({AGENTA_SERVICES_URL}/{key}/{version}). The backend was supposed to never store a URL
for them — but the read path (_normalize_revision_for_read, _get_service_url,
_build_simple_workflow_data) only recomputed the URL when data.url was empty, and
the commit path only skipped setting one (it never stripped an incoming value).

On 2026-06-29 an agent self-update via the commit_revision platform op persisted
data.url = http://agenta-agent:8000/v0/invoke (the agent service's internal address)
onto a builtin agent revision. Because that value was non-empty, the API served it
verbatim, and the browser cannot resolve agenta-agent. Stored DB values confirmed it:

agenta:builtin:agent:v0 | http://agenta-agent:8000/v0/invoke   (v7-v9, agent QA self-commits)
agenta:builtin:agent:v0 | http://144.76.237.122:8280/services/agent/v0   (v2, v6, UI)

The FE workaround added in #4936 (invocationUrlAtomFamily routing builtins through
resolveBuiltinAppServiceUrl) only ever covered completion/chat, never agent, so it
did not fix the broken case — and it was unnecessary for completion/chat, which had
run for ~3 months on the plain data.url path since #3883 removed that helper from invoke.

Change

  • Backend (root cause): add _resolve_service_url, which always recomputes the
    canonical URL from the URI for builtin apps/evaluators (ignoring any stored value), and
    keeps the stored value for non-builtin (custom) workflows. Use it on all read paths. On
    commit, drop any incoming data.url for builtin apps so a bad value is never persisted
    again.
  • Frontend: revert the [fix] Harden agent round-trip QA paths #4936 invocationUrlAtomFamily hunk so builtins use the normal
    data.url / URI path. (resolveBuiltinAppServiceUrl stays; it is still used by
    deploymentUrlAtomFamily and store.ts.)

Verification

  • New unit test test_service_url_resolution.py (8 cases) + existing
    test_retrieve_resolve.py green.
  • Live EE dev stack: the previously broken agent revision (v9, project pi-agents) now
    invokes against http://144.76.237.122:8280/services/agent/v0/invokeHTTP 200.
    The run executes end to end; the only remaining failure is a downstream Anthropic model
    error ("thinking.type.enabled" is not supported for this model), unrelated to this fix.

https://claude.ai/code/session_014emxsaD8EFiNP3m3f56nhD

@dosubot dosubot Bot added size:M This PR changes 30-99 lines, ignoring generated files. Backend labels Jun 30, 2026
@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8ab99f5c-db6b-4fa4-9bec-ecd793b0c2fd

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

A new _resolve_service_url helper centralizes builtin vs. non-builtin workflow service URL logic in the backend. It is wired into the read normalization, service URL derivation, commit persistence, and simple-data construction paths. The frontend invocationUrlAtomFamily drops the builtin proxy routing and uses the same stored/derived URL pattern. Unit tests cover all resolution cases.

Changes

Service URL Resolution

Layer / File(s) Summary
_resolve_service_url helper and commit-time persistence
api/oss/src/core/workflows/service.py
Adds _resolve_service_url that recomputes builtin URLs from uri and preserves non-builtin stored URLs; updates _build_simple_workflow_data to use it. In commit_workflow_revision, builtin snippets force data.url = None and non-builtin snippets infer data.url from uri when absent.
Read and invoke paths wired to shared resolver
api/oss/src/core/workflows/service.py
_normalize_revision_for_read and _get_service_url both now call _resolve_service_url instead of independently inferring URLs.
Frontend invocation URL atom
web/packages/agenta-entities/src/workflow/state/runnableSetup.ts
invocationUrlAtomFamily removes resolveBuiltinAppServiceUrl and always derives the base URL from data.url or buildServiceUrlFromUri(data.uri), then appends /invoke.
Unit tests
api/oss/tests/pytest/unit/workflows/test_service_url_resolution.py
Covers builtin URI recomputation over stale URLs, idempotency, evaluator recomputation, custom URI preservation, URL-only revision retention, and None input.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: serving canonical builtin workflow service URLs instead of stored Docker-internal hosts.
Description check ✅ Passed The description is directly about the workflow service URL fix and aligns with the backend and frontend changes in the pull request.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/builtin-invoke-url

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@vercel

vercel Bot commented Jun 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agenta-documentation Ready Ready Preview, Comment Jun 30, 2026 5:49pm

Request Review

@mmabrouk mmabrouk added the needs-review Agent updated; awaiting Mahmoud's review label Jun 30, 2026
@mmabrouk

Copy link
Copy Markdown
Member Author

🤖 The AI agent says:

What I need reviewed

This removes the #4936 builtin-invoke FE workaround and fixes the real root cause in the
backend. Please focus on:

  1. _resolve_service_url correctness for non-app builtins. It always recomputes the
    URL for any agenta:builtin:* URI — including builtin evaluators
    (auto_exact_match, auto_ai_critique, …). Stored evaluator URLs in the DB are either
    empty or already canonical, so this is a no-op for them, but confirm there is no
    evaluator that intentionally carries a non-canonical data.url that this would
    override.

  2. Commit-path strip. For builtin apps the commit now sets data.url = None before
    persisting. Confirm nothing downstream of commit_workflow_revision relies on a stored
    builtin URL at write time (read paths recompute it, so the served value is unchanged).

  3. _get_service_url (backend server-side invoke). It now recomputes for builtins
    too. The backend-internal invoke could previously reach agenta-agent:8000; verify
    forcing it onto the public {services_url} origin is correct for the worker/eval invoke
    paths, not just the browser.

Verified: new unit tests + live EE dev — the previously broken agent app now invokes
at …/services/agent/v0/invoke (HTTP 200); only a downstream model error remains
(thinking.type.enabled unsupported), which is out of scope here.

A separate question for a maintainer: should we also ship a one-off data migration to null
out the 3 already-poisoned data.url rows, or is correct-on-read enough? I left them as-is
since the read path now masks them and no new ones can be written.

@mmabrouk

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1e69783d-2d36-4bce-b594-b18daac74cd9

📥 Commits

Reviewing files that changed from the base of the PR and between dce5e8b and 65cbca9.

📒 Files selected for processing (3)
  • api/oss/src/core/workflows/service.py
  • api/oss/tests/pytest/unit/workflows/test_service_url_resolution.py
  • web/packages/agenta-entities/src/workflow/state/runnableSetup.ts

Comment thread api/oss/src/core/workflows/service.py Outdated
Comment on lines +428 to +433
if revision.data:
resolved_url = _resolve_service_url(revision_data=revision.data)
if resolved_url and resolved_url != revision.data.url:
revision = revision.model_copy(
update={
"data": revision.data.model_copy(
update={"url": env.agenta.services_url.rstrip("/") + path}
)
"data": revision.data.model_copy(update={"url": resolved_url})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Ensure builtin URLs never fall back to stored values.

Line 2358 still returns the original revision_data.url for a builtin URI when infer_url_from_uri() returns no path. That preserves the poisoned Docker/internal URL this PR is trying to mask. Also update the read/simple-data callers to clear stale URLs when the resolver returns None, instead of only overwriting on truthy values.

Proposed fix
-        if resolved_url and resolved_url != revision.data.url:
+        if resolved_url != revision.data.url:
             revision = revision.model_copy(
                 update={
                     "data": revision.data.model_copy(update={"url": resolved_url})
                 }
             )
-    if uri:
-        _, kind, _, _ = parse_uri(uri)
-        if kind == "builtin" or not url:
-            path = infer_url_from_uri(uri)
-            if path:
-                url = env.agenta.services_url.rstrip("/") + path
-
-    return url
+    if not uri:
+        return url
+
+    _, kind, _, _ = parse_uri(uri)
+    path = infer_url_from_uri(uri)
+
+    if kind == "builtin":
+        return env.agenta.services_url.rstrip("/") + path if path else None
+
+    if not url and path:
+        return env.agenta.services_url.rstrip("/") + path
+
+    return url
     resolved_url = _resolve_service_url(revision_data=revision_data)
     if resolved_url:
         data_dict["url"] = resolved_url
+    elif resolved_url != revision_data.url:
+        data_dict.pop("url", None)
     return SimpleWorkflowData(**data_dict)

Also applies to: 2339-2375

Comment on lines +251 to 253
// Resolve service URL: prefer stored url, fall back to building from URI
const serviceUrl =
entity.data.url?.replace(/\/+$/, "") ?? buildServiceUrlFromUri(entity.data.uri)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Treat blank URLs as missing before falling back to the URI.

With ??, data.url: "" skips buildServiceUrlFromUri(...), so a valid URI can still produce no invocation URL.

Proposed fix
-        const serviceUrl =
-            entity.data.url?.replace(/\/+$/, "") ?? buildServiceUrlFromUri(entity.data.uri)
+        const storedServiceUrl = entity.data.url?.trim().replace(/\/+$/, "")
+        const serviceUrl = storedServiceUrl || buildServiceUrlFromUri(entity.data.uri)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Resolve service URL: prefer stored url, fall back to building from URI
const serviceUrl =
entity.data.url?.replace(/\/+$/, "") ?? buildServiceUrlFromUri(entity.data.uri)
// Resolve service URL: prefer stored url, fall back to building from URI
const storedServiceUrl = entity.data.url?.trim().replace(/\/+$/, "")
const serviceUrl = storedServiceUrl || buildServiceUrlFromUri(entity.data.uri)

@mmabrouk mmabrouk left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Backend (root cause): add _resolve_service_url, which always recomputes the
canonical URL from the URI for builtin apps/evaluators (ignoring any stored value), and
keeps the stored value for non-builtin (custom) workflows. Use it on all read paths. On
commit, drop any incoming data.url for builtin apps so a bad value is never persisted
again.

I don't think we need to make any changes to the backend. The issue was in the agent making a bad call and calling this endpoint. Let's not change the business logic for the backend, so please revert the change and just revert the frontend change there.

@mmabrouk mmabrouk removed the needs-review Agent updated; awaiting Mahmoud's review label Jun 30, 2026
…host

Builtin app/evaluator service URLs are deterministic from the URI
(`{services_url}/{key}/{version}`), but the read path only recomputed them
when `data.url` was empty. An agent self-update (`commit_revision`) could
persist a Docker-internal host (`http://agenta-agent:8000/v0/invoke`) into
`data.url`, which the API then served verbatim; the browser could not resolve
it (ERR_NAME_NOT_RESOLVED on the builtin `/invoke` call).

- Resolve builtin URLs from the URI on every read (`_resolve_service_url`),
  ignoring any stored value; non-builtin URLs keep the stored value.
- Stop persisting a URL for builtin apps on commit.
- Revert the PR #4936 `invocationUrlAtomFamily` FE workaround so builtins use
  the normal `data.url` / URI path again (it only ever covered completion/chat,
  never agent, so it did not even fix the broken case).

Claude-Session: https://claude.ai/code/session_014emxsaD8EFiNP3m3f56nhD
@mmabrouk
mmabrouk force-pushed the fix/builtin-invoke-url branch from 65cbca9 to b955fd6 Compare June 30, 2026 17:48
@dosubot dosubot Bot added size:S This PR changes 10-29 lines, ignoring generated files. and removed size:M This PR changes 30-99 lines, ignoring generated files. labels Jun 30, 2026
@mmabrouk
mmabrouk merged commit 19e0f2d into big-agents Jun 30, 2026
12 of 14 checks passed
@mmabrouk

Copy link
Copy Markdown
Member Author

🤖 The AI agent says:

Shipped as FE-revert-only, per the owner's review. The commit now contains just the runnableSetup.ts change that removes the PR #4936 invocationUrlAtomFamily builtin-URL workaround, so builtins go back through the normal data.url / URI path.

The backend change to api/oss/src/core/workflows/service.py (and its new unit test) was reverted out of this PR: there is no core business-logic change here. The root cause is the agent persisting a bad Docker-internal URL via commit_revision, and that is handled in the commit_revision tool, not in the core workflows router. Merged into big-agents.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Backend size:S This PR changes 10-29 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant