Skip to content

fix(team): fall through to cached catalog when live slash-command list is empty - #705

Closed
bbt567 wants to merge 1 commit into
iOfficeAI:mainfrom
bbt567:fix/team-slash-catalog-fallthrough
Closed

fix(team): fall through to cached catalog when live slash-command list is empty#705
bbt567 wants to merge 1 commit into
iOfficeAI:mainfrom
bbt567:fix/team-slash-catalog-fallthrough

Conversation

@bbt567

@bbt567 bbt567 commented Jul 28, 2026

Copy link
Copy Markdown

Root cause

In team mode, a backend-native slash command such as /compact is silently treated as ordinary text and wrapped in the ## New Messages wake payload, so the agent never receives the bare command and replies conversationally instead of executing it.

resolve_slash_catalog degrades live → cached → static. The live arm short-circuits on any Ok result from the session, including an empty list:

Ok(items) => {
    let names = items.into_iter().map(|item| item.command).collect();
    return Some((names, SlashCatalogSource::Live));   // returns even when empty
}

A backend's live slash_commands can be transiently empty before its discovery handshake completes. For the claude backend, ClaudeSessionBackend::capabilities() merges discovered_caps.slash_commands only once the control_request{initialize} control response lands (sniff_control_initialize); until then it is []. Returning that empty live list yields CatalogEmpty, which disables every team slash command and forces the wrapped-wake fallback — even though the same commands are already present in the persisted agent_metadata.available_commands snapshot (chain arm b).

Evidence (real captured data, not inference)

Reproduced on v2.1.41+ with a team whose Leader is claude (transport: CliAssumed) and a member is codex.

1. The failure reply is generated by the agent, not AionCore. The string `/compact` 是内置命令… does not exist anywhere in this repo. The persisted claude thinking block shows the agent reasoning about it but not executing:

"The user typed /compact which is a built-in CLI command… I should acknowledge this is a built-in command and not invoke any skill…"

2. The same backend executes /compact correctly when sent bare (non-team). A direct (non-team) claude conversation returns Not enough messages to compact. — i.e. the native compact path ran. The team path differs only in that the command is wrapped, so claude never sees a bare /compact.

3. The persisted snapshot already advertises compact. agent_metadata.available_commands for the claude agent (2d23ff1c) contains {"name":"compact","description":"Free up context by summarizing the conversation so far"}. So arm (b) of the chain would resolve the command — it is never reached because arm (a) returns the empty live list first.

Fix

Adopt the live catalog only when non-empty. An empty live list falls through to cached/static. The decision is extracted into live_catalog_or_none so the degradation boundary has a unit test (the chain previously had none — which is how this slipped in). A backend that genuinely advertises no commands still degrades correctly: the cached snapshot is empty or the chain ends at None, both yielding the wrapped wake.

This makes no claim about any external CLI's behavior — it only stops a transiently-empty live list from masking a populated persisted snapshot.

Verification

  • cargo check -p aionui-app
  • cargo test -p aionui-app --lib → 18 passed (incl. new live_catalog_adopted_only_when_non_empty) ✅
  • cargo test -p aionui-team --test e2e_team_flow → 29 passed, 0 failed — all existing catalog tests (recognized_command_is_sent_bare_to_member/lead, unrecognized_slash_and_plain_text_fall_back_to_wrapped_wake, catalog_unavailable_falls_back_to_wrapped_wake, empty_catalog_logs_warn_and_falls_back_to_wrapped_wake) still green ✅

Closes #3750.

…t is empty

In team mode, sending a backend-native slash command like /compact was
silently treated as ordinary text and wrapped in the '## New Messages'
wake payload, so the agent never received the bare command and replied
conversationally instead of executing it (e.g. claude: '/compact is a
built-in command...').

Root cause: resolve_slash_catalog's degradation chain short-circuited on
ANY Ok result from the live session, including an empty list. A backend's
live slash_commands can be transiently empty before its discovery
handshake completes (e.g. claude's control_request{initialize} response
has not landed, so discovered_caps.slash_commands is still []), yet the
same commands are already present in the persisted
agent_metadata.available_commands snapshot. Returning the empty live list
yielded CatalogEmpty, which disabled every team slash command and forced
the wrapped-wake fallback — masking commands the backend genuinely
supports.

Fix: adopt the live catalog only when non-empty (extracted into
live_catalog_or_none for testability). An empty live list now falls
through to cached/static. A backend that genuinely advertises no commands
still degrades correctly (empty cached snapshot or final None).

Verified: claude's persisted snapshot advertises 'compact'; a direct
(non-team) claude session executes /compact ('Not enough messages to
compact'), proving the backend supports it when sent bare.

Closes #3750
@bbt567 bbt567 closed this Jul 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant