GitHub Code Quality bot flagged the empty `except (AttributeError,
TypeError): pass` at workspace/a2a_executor.py:424 as a nit on PR #1783.
The suppression IS intentional — `new_agent_text_message()` returns
a plain string in MagicMock paths in tests where assignment to
`.metadata` raises despite hasattr being true.
This:
- Adds a why-comment citing the test-mock motivation, commit
dcbcf19 (the original guard), and issue #1787 so the next
code-quality pass doesn't re-flag it.
- Adds `logger.debug("metadata attach skipped (non-Message ...")`
for observability — debug-level so production logs stay quiet
but ops can flip the level if metadata loss is ever suspected.
Behavior unchanged. 43 existing a2a_executor tests still pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
If asyncio.CancelledError arrived during the heartbeat HTTP push inside
set_current_task() (the increment call), the code raised before entering
the try/finally block in _execute_locked. The finally block never ran,
so active_tasks stayed at 1 forever. Every subsequent heartbeat reported
active_tasks=1, the server saw active_tasks < max_concurrent_tasks as
false (1 < 1), and DrainQueueForWorkspace never fired. Queued A2A
requests were permanently stuck.
Fix: move set_current_task(increment) to be the FIRST statement inside
the try block, not before it. set_current_task's synchronous portion
(heartbeat.active_tasks mutation) still runs unconditionally; only the
optional HTTP push can be cancelled. The finally block now always runs
and always decrements active_tasks back to 0.
Affected executors: claude_sdk_executor, cli_executor, a2a_executor.
hermes_executor is not affected (does not call set_current_task).
Root cause of today's "active_tasks: 1 + queue drain never triggers"
P1 pattern across three workspaces.
All 167 executor tests pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
new_agent_text_message returns a real Message object in production but
some test mocks return a plain string. Guard with hasattr + try/except
so the tool_trace assignment doesn't crash test_non_stream_events_ignored.
BLOCKERS fixed:
- instructions.go: Drop team-scope queries (teams/team_members tables don't
exist in any migration). Schema column kept for future. Restored Resolve
to /workspaces/:id/instructions/resolve under wsAuth — closes auth gap
that allowed cross-workspace enumeration of operator policy.
- migration 040: Add CHECK constraints on title (<=200) and content (<=8192)
to prevent token-budget DoS via oversized instructions.
- a2a_executor.py: Pair on_tool_start/on_tool_end via run_id instead of
list-position so parallel tool calls don't drop or clobber outputs. Cap
tool_trace at 200 entries to prevent runaway loops bloating JSONB.
HIGH fixes:
- instructions.go: Add length validation in Create + Update handlers.
Removed dead rows_ shadow variable. Replaced string concatenation in
Resolve with strings.Builder.
- prompt.py: Drop httpx timeout 10s -> 3s (boot hot path). Switch print
to logger.warning. Add Authorization bearer header from
MOLECULE_WORKSPACE_TOKEN env var.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Every A2A response now includes a tool_trace — the list of tools/commands
the agent actually invoked during execution. This enables verifying agent
claims against what they actually did, catches hallucinated "I checked X"
responses, and provides an audit trail for the CEO to control hundreds of
agents by checking the top-level PM's trace.
Changes:
- Python runtime: collect tool name/input/output_preview on every
on_tool_start/on_tool_end event, embed in Message.metadata.tool_trace
- Go platform: extract tool_trace from A2A response metadata, store in
new activity_logs.tool_trace JSONB column with GIN index
- Activity API: expose tool_trace in List and broadcast endpoints
- Migration 039: adds tool_trace column + GIN index
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>