iOfficeAI / iOfficeAI/AionCore
[Bug] team_task_list emits null fields (description/owner), hanging the calling agent — exec_members already guards against this
- Dominant language
- Rust
- Stars
- 105
- Forks
- 169
- Avg merge
- 5h 58m
- Merged PRs (30d)
- 84
Description
## 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.rs` → `dispatch_tool` → `exec_task_list(scheduler).await`
- → `scheduler::list_tasks` → `task_board.list_tasks(&team_id).await` → `repo.list_tasks(...).await`
- `TaskBoard` holds only `repo: Arc` — **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:
```rust
json!({
"id": t.id,
"subject": t.subject,
"description": t.description, // Option -> serializes to JSON `null` when None
"status": t.status,
"owner": t.owner, // Option -> 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:
```rust
// ...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:
```rust
"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")
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.