Skip to content

[Bug] team_task_list emits null fields (description/owner), hanging the calling agent — exec_members already guards against this #390

@crystallyf1

Description

@crystallyf1

Summary

Calling the team_task_list MCP tool causes the calling agent's session to hang / become unresponsive (and spawned sub-agents to crash). This is already reported as a symptom in iOfficeAI/AionUi#3136, but without a root cause. After auditing the aionui-team crate, I believe I've found the concrete differentiating cause and a one-line-style fix that mirrors an existing guard the team already applied to team_members.

Environment

  • AionCore: built from bundled-aioncore shipped with AionUi (observed on the v0.1.17 / v0.1.18 line; code paths below are present on main)
  • OS: Windows 11 (x64)
  • Affected backends: most visible with Gemini-backed teammates; the teammate system prompt (crates/aionui-team/src/prompts/teammate.rs) explicitly instructs new teammates to "Use team_task_list and team_members to check current team state.", so freshly spawned teammates hit this almost immediately.

Steps to Reproduce

  1. Create a team with at least one teammate (any spawned sub-agent).
  2. Have the teammate call team_task_list (it will do so on its own, per the teammate system prompt).
  3. The teammate's turn never completes — the session hangs / goes unresponsive. The Lead cannot detect the dead sub-agent.

Expected

team_task_list returns the task list and the agent turn completes normally, exactly like team_members does.

Actual

The calling agent hangs. team_members and team_send_message (called the same way) do not hang.

Root Cause Analysis

This is NOT a server-side deadlock. I traced the call path and the team_task_list path actually takes fewer locks than team_members:

  • crates/aionui-team/src/mcp/server.rsdispatch_toolexec_task_list(scheduler).await
  • scheduler::list_taskstask_board.list_tasks(&team_id).awaitrepo.list_tasks(...).await
  • TaskBoard holds only repo: Arc<dyn ITeamRepository>no Mutex/RwLock, no lock held across an .await.

The actual differentiator is null fields in the tool result. In exec_task_list, the response object is built with the json! macro directly:

json!({
    "id": t.id,
    "subject": t.subject,
    "description": t.description, // Option<String> -> serializes to JSON `null` when None
    "status": t.status,
    "owner": t.owner,             // Option<String> -> serializes to JSON `null` when unassigned
    "blocked_by": t.blocked_by,
    "blocks": t.blocks,
})

Even though TeamTask carries #[serde(skip_serializing_if = "Option::is_none")] on its fields, that attribute is bypassed here because the response is hand-built with json! instead of serializing the struct. So description: null / owner: null are emitted to the MCP client.

The smoking gun: exec_members in the same file already guards against exactly this, with a comment explaining why:

// ...so MCP clients never see `null` and misread a live teammate as offline.
let status = a.status.unwrap_or(TeammateStatus::Idle);

So the project has already hit "null in an MCP tool result breaks the client" once, fixed it for team_members, but team_task_list was never given the same treatment. team_members / team_send_message don't hang precisely because their results contain no null (send_message returns a plain string).

The client-side failure mode (Gemini/ACP turn handling choking on null/undefined tool-call results and leaving an orphaned, never-completing tool call) is consistent with several existing front-end guards in iOfficeAI/AionUi (e.g. PRs that ignore tool calls without call IDs / non-renderable stream events) and with known Gemini null/undefined functionResponse issues.

Evidence strength (being honest)

  • "Not a server-side deadlock; list_tasks takes fewer locks than list_agents" — confirmed from source
  • "exec_task_list emits null; exec_members deliberately avoids null" — confirmed from source
  • "The null is what hangs the front-end agent turn" — strong inference (backed by the existing exec_members guard + comment, the front-end guard PRs, and known Gemini null-handling issues), but I have not captured a runtime trace pinning the exact front-end crash line.

Suggested Fix (mirrors the existing exec_members guard)

In crates/aionui-team/src/mcp/server.rs, exec_task_list, stop emitting bare null for the optional string fields:

"description": t.description.clone().unwrap_or_default(),
"owner":       t.owner.clone().unwrap_or_default(),

(or build the json! object conditionally so None keys are omitted). This makes team_task_list output null-free, consistent with team_members. A more thorough/defensive fix would also harden the front-end tool-result normalization so that any MCP tool returning null can't strand a tool call — but the server-side change above is the minimal, low-risk fix matching a pattern the team has already validated.

Related

  • Symptom report: iOfficeAI/AionUi#3136 ("Bug: team_task_list tool causes spawned sub-agents to crash")

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions