From 778a6c392a143a05f048435d31e67bc943253336 Mon Sep 17 00:00:00 2001 From: hzhang Date: Thu, 11 Jun 2026 09:29:52 +0100 Subject: [PATCH] fix(calendar): read agent status by agent_id alone (cross-agent gate) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GET /calendar/agent/status went through _require_agent, which filters by agent_id AND claw_identifier. But this is a cross-agent READ — the caller asks about a DIFFERENT agent and passes its OWN X-Claw-Identifier (the CLI falls back to hostname). So the query required target.claw == caller's-hostname and spuriously 404'd whenever they differed: `hf agent status-of ` worked only when the caller's host happened to equal the target's registered claw (e.g. fine on the node whose hostname matched, 404 everywhere else). This broke the delegate-task / on-call-handoff status gates. Look the agent up by agent_id alone for the read; accept but ignore X-Claw-Identifier (now optional). The POST (write own status) still identifies the caller by agent_id + claw via _require_agent. Verified: `hf agent status-of ` now returns {agent_id, status} from any caller/host; unknown agent still 404s. Co-Authored-By: Claude Opus 4.8 (1M context) --- app/api/routers/calendar.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/app/api/routers/calendar.py b/app/api/routers/calendar.py index 4d5d837..0117f0f 100644 --- a/app/api/routers/calendar.py +++ b/app/api/routers/calendar.py @@ -568,17 +568,28 @@ def agent_update_virtual_slot( ) def get_agent_status( agent_id: str = Query(..., description="Target agent_id"), - x_claw_identifier: str = Header(..., alias="X-Claw-Identifier"), + x_claw_identifier: str = Header(None, alias="X-Claw-Identifier"), db: Session = Depends(get_db), ): """Return `{agent_id, status}` so callers (Fabric.OpenclawPlugin's - triage on-call gate, etc.) can decide whether the agent is currently - eligible without flipping their state. + triage on-call gate, delegate-task / on-call-handoff status gates, etc.) + can decide whether the agent is currently eligible without flipping + their state. + + This is a cross-agent READ: look the agent up by `agent_id` ALONE. We do + NOT scope by the caller's claw_identifier — the caller is asking about a + DIFFERENT agent, and the X-Claw-Identifier the CLI sends is the caller's + own (often just a hostname fallback), so requiring target.claw == that + header spuriously 404'd whenever the two claws differed (e.g. any + `hf agent status-of` from a host whose hostname != the target's + registered claw). The header is accepted but ignored. No-op for unknown agents — returns 404 with `{detail: 'Agent not found'}` so the caller can decide whether to fail-open or fail-closed. """ - agent = _require_agent(db, agent_id, x_claw_identifier) + agent = db.query(Agent).filter(Agent.agent_id == agent_id).first() + if agent is None: + raise HTTPException(status_code=404, detail="Agent not found") return { "agent_id": agent.agent_id, "status": agent.status.value if hasattr(agent.status, 'value') else str(agent.status),