fix(workspace-template): #220 — send auth_headers() on initial_prompt + idle loop

Closes #220. #215 added auth_headers() to /registry/register but missed
two other self-post paths from the same workspace container:

1. initial_prompt (_do_send_sync) — fires once on first boot after the
   A2A server is ready. Posts to /workspaces/:id/a2a via the platform
   proxy. Missing headers meant the initial prompt got silently
   dropped as 401 on any token-enrolled workspace.

2. idle loop (_post_sync) — fires every idle_interval_seconds while
   the workspace has no active task (#205 pattern). Same proxy path,
   same missing headers, same silent 401 in multi-tenant mode.

Both now build headers as
  {"Content-Type": "application/json", **auth_headers()}

auth_headers() returns {"Authorization": "Bearer <token>"} when
/auth-token.txt exists, empty dict otherwise (first boot before
register issues the token). The existing lazy-bootstrap fail-open
on the platform side covers the empty-dict case.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Hongming Wang 2026-04-15 12:02:01 -07:00
parent 533beb8da3
commit 1c41c30310

View File

@ -347,6 +347,12 @@ async def main(): # pragma: no cover
},
}).encode()
# #220: include platform bearer token so the request isn't
# silently rejected once any workspace has a live token on
# file. Without this, initial_prompt 401s in multi-tenant
# mode exactly like /registry/register did in #215.
headers = {"Content-Type": "application/json", **auth_headers()}
# Retry with backoff — the platform proxy may not be able to
# reach us yet (container networking takes a moment to settle).
max_retries = 5
@ -355,7 +361,7 @@ async def main(): # pragma: no cover
req = urllib.request.Request(
f"{platform_url}/workspaces/{workspace_id}/a2a",
data=payload,
headers={"Content-Type": "application/json"},
headers=headers,
)
with urllib.request.urlopen(req, timeout=600) as resp:
resp.read()
@ -435,11 +441,14 @@ async def main(): # pragma: no cover
def _post_sync():
# Returns (status_code, error_type) so the caller logs the
# actual outcome instead of a bare "post failed" line.
# #220: include auth_headers() on every idle fire. Without
# this, the idle loop 401s in multi-tenant mode.
headers = {"Content-Type": "application/json", **auth_headers()}
try:
req = _urlreq.Request(
f"{platform_url}/workspaces/{workspace_id}/a2a",
data=payload,
headers={"Content-Type": "application/json"},
headers=headers,
)
with _urlreq.urlopen(req, timeout=IDLE_FIRE_TIMEOUT_SECONDS) as resp:
resp.read()