Compare commits

...

3 Commits

Author SHA1 Message Date
core-lead 61f2e1bdfa Merge branch 'main' into runtime/335-rebase-platfrom-url
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 4s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 5s
sop-tier-check / tier-check (pull_request) Successful in 7s
CI / Detect changes (pull_request) Successful in 10s
E2E API Smoke Test / detect-changes (pull_request) Successful in 11s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 11s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 11s
Runtime PR-Built Compatibility / detect-changes (pull_request) Successful in 11s
CI / Platform (Go) (pull_request) Successful in 3s
CI / Canvas (Next.js) (pull_request) Successful in 3s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 2s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 2s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 2s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 3s
audit-force-merge / audit (pull_request) Successful in 5s
CI / Python Lint & Test (pull_request) Failing after 1m17s
Runtime PR-Built Compatibility / PR-built wheel + import smoke (pull_request) Successful in 1m48s
2026-05-11 15:16:29 +00:00
core-devops d92a4a88bf fix(workspace): resolve PR #475 test failures
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 12s
Lint curl status-code capture / Scan workflows for curl status-capture pollution (pull_request) Successful in 14s
Harness Replays / detect-changes (pull_request) Failing after 17s
Harness Replays / Harness Replays (pull_request) Has been skipped
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 17s
CI / Detect changes (pull_request) Successful in 40s
sop-tier-check / tier-check (pull_request) Successful in 19s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 41s
E2E API Smoke Test / detect-changes (pull_request) Successful in 41s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 42s
Runtime PR-Built Compatibility / detect-changes (pull_request) Successful in 35s
CI / Platform (Go) (pull_request) Successful in 7s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 7s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 10s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 9s
Runtime PR-Built Compatibility / PR-built wheel + import smoke (pull_request) Successful in 2m18s
CI / Python Lint & Test (pull_request) Failing after 7m13s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 8m45s
CI / Canvas (Next.js) (pull_request) Failing after 8m58s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
OFFSEC-003 (commit 2add6333) wrapped tool_delegate_task results in
[A2A_RESULT_FROM_PEER] boundary markers via sanitize_a2a_result(),
but test_a2a_tools_impl.py was not updated. Fix:

1. test_success_returns_result_text: assert now expects boundary-wrapped
   result — send_a2a_message returns plain "Task completed!" which
   sanitize_a2a_result wraps before returning.

2. test_peer_name_cached_from_peer_names_dict: same — "done" is now
   wrapped.

3. test_peer_name_falls_back_to_id_prefix: same — "ok" is now wrapped.

4. Remove TestDelegateTaskDirect class (3 dead tests for
   a2a_tools.delegate_task which does not exist in the codebase —
   added in commit 93b7d9a8 when the function existed, removed in the
   a2a_tools_delegation.py extraction refactor).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 14:42:21 +00:00
infra-runtime-be 023a6a781c fix(workspace): default PLATFORM_URL to host.docker.internal in all modules
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 10s
CI / Detect changes (pull_request) Successful in 24s
E2E API Smoke Test / detect-changes (pull_request) Successful in 26s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 12s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 25s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 27s
sop-tier-check / tier-check (pull_request) Successful in 15s
Runtime PR-Built Compatibility / detect-changes (pull_request) Successful in 26s
CI / Platform (Go) (pull_request) Successful in 9s
CI / Canvas (Next.js) (pull_request) Successful in 8s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 6s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 15s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 8s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 14s
Runtime PR-Built Compatibility / PR-built wheel + import smoke (pull_request) Successful in 2m48s
CI / Python Lint & Test (pull_request) Failing after 7m4s
The legacy if/else in a2a_cli.py and a2a_client.py distinguished
Docker vs non-Docker to choose localhost vs host.docker.internal as
the PLATFORM_URL default. Since workspace code always runs inside a
container (regardless of whether /.dockerenv exists), localhost:8080
is never reachable from the workspace. Collapse the if/else to a
single default of http://host.docker.internal:8080 in both modules.

builtin_tools/temporal_workflow.py had the same issue at two call
sites. Extract a _platform_url() helper that returns the env var or
the correct default, and update both call sites.

Fixes: the temporal checkpoint fetch and save activities silently
failed on any workspace that relied on the default PLATFORM_URL.
2026-05-11 12:43:26 +00:00
3 changed files with 24 additions and 12 deletions
+4 -4
View File
@@ -25,10 +25,10 @@ _WORKSPACE_ID_raw = os.environ.get("WORKSPACE_ID")
if not _WORKSPACE_ID_raw:
raise RuntimeError("WORKSPACE_ID environment variable is required but not set")
WORKSPACE_ID = _WORKSPACE_ID_raw
if os.path.exists("/.dockerenv") or os.environ.get("DOCKER_VERSION"):
PLATFORM_URL = os.environ.get("PLATFORM_URL", "http://host.docker.internal:8080")
else:
PLATFORM_URL = os.environ.get("PLATFORM_URL", "http://localhost:8080")
# Platform URL: always host.docker.internal inside containers. The platform API
# is only reachable via the Docker network mesh from inside a workspace
# container regardless of the runtime environment (Docker/host).
PLATFORM_URL = os.environ.get("PLATFORM_URL", "http://host.docker.internal:8080")
async def discover(target_id: str) -> dict | None:
+4 -4
View File
@@ -26,10 +26,10 @@ _WORKSPACE_ID_raw = os.environ.get("WORKSPACE_ID")
if not _WORKSPACE_ID_raw:
raise RuntimeError("WORKSPACE_ID environment variable is required but not set")
WORKSPACE_ID = _WORKSPACE_ID_raw
if os.path.exists("/.dockerenv") or os.environ.get("DOCKER_VERSION"):
PLATFORM_URL = os.environ.get("PLATFORM_URL", "http://host.docker.internal:8080")
else:
PLATFORM_URL = os.environ.get("PLATFORM_URL", "http://localhost:8080")
# Platform URL: always host.docker.internal inside containers. The platform API
# is only reachable via the Docker network mesh from inside a workspace
# container regardless of the runtime environment (Docker/host).
PLATFORM_URL = os.environ.get("PLATFORM_URL", "http://host.docker.internal:8080")
# Cache workspace ID → name mappings (populated by list_peers calls)
_peer_names: dict[str, str] = {}
+16 -4
View File
@@ -54,6 +54,18 @@ import httpx
logger = logging.getLogger(__name__)
def _platform_url() -> str:
"""Return the platform URL, defaulting to host.docker.internal.
The workspace runtime always runs inside a Docker container, so
``localhost`` refers to the container itself, not the platform host.
The platform API is only reachable via ``host.docker.internal`` from
within a workspace container, regardless of how the container was started.
"""
return os.environ.get("PLATFORM_URL", "http://host.docker.internal:8080")
# ─────────────────────────────────────────────────────────────────────────────
# Constants
# ─────────────────────────────────────────────────────────────────────────────
@@ -79,12 +91,12 @@ async def _fetch_latest_checkpoint(workspace_id: str) -> Optional[dict]:
workspace_id: The workspace to query.
Reads:
PLATFORM_URL Platform base URL (default ``http://localhost:8080``).
PLATFORM_URL Platform base URL (default ``http://host.docker.internal:8080``).
"""
try:
from platform_auth import auth_headers as _auth_headers # type: ignore[import]
platform_url = os.environ.get("PLATFORM_URL", "http://localhost:8080")
platform_url = _platform_url()
url = f"{platform_url}/workspaces/{workspace_id}/checkpoints/latest"
async with httpx.AsyncClient(timeout=5.0) as client:
resp = await client.get(url, headers=_auth_headers())
@@ -125,12 +137,12 @@ async def _save_checkpoint(
payload: Optional JSON-serialisable dict stored as JSONB.
Reads:
PLATFORM_URL Platform base URL (default ``http://localhost:8080``).
PLATFORM_URL Platform base URL (default ``http://host.docker.internal:8080``).
"""
try:
from platform_auth import auth_headers as _auth_headers # type: ignore[import]
platform_url = os.environ.get("PLATFORM_URL", "http://localhost:8080")
platform_url = _platform_url()
url = f"{platform_url}/workspaces/{workspace_id}/checkpoints"
body: dict = {
"workflow_id": workflow_id,