Skip to content

Discord: allow /acp inline action#32777

Closed
arjunaskykok wants to merge 1 commit intoopenclaw:mainfrom
arjunaskykok:fix/discord-acp-autocomplete
Closed

Discord: allow /acp inline action#32777
arjunaskykok wants to merge 1 commit intoopenclaw:mainfrom
arjunaskykok:fix/discord-acp-autocomplete

Conversation

@arjunaskykok
Copy link
Copy Markdown

Summary

Describe the problem and fix in 2–5 bullets:

  • Problem: Discord slash command for /acp uses static choices, so inline args like /acp close send action=null and always show the menu.
  • Why it matters: Users can’t bypass the picker for common actions; regression in inline usage.
  • What changed: Force autocomplete for the /acp action option so Discord passes typed values through.
  • What did NOT change (scope boundary): No changes to ACP command parsing or menu behavior when no action is provided.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

User-visible / Behavior Changes

  • Discord /acp accepts inline action values (e.g. /acp close) without forcing the picker menu.

Security Impact (required)

  • New permissions/capabilities? (No)
  • Secrets/tokens handling changed? (No)
  • New/changed network calls? (No)
  • Command/tool execution surface changed? (No)
  • Data access scope changed? (No)
  • If any Yes, explain risk + mitigation:

Repro + Verification

Environment

  • OS: Windows/macOS (Discord)
  • Runtime/container: N/A
  • Model/provider: N/A
  • Integration/channel (if any): Discord
  • Relevant config (redacted): N/A

Steps

  1. In Discord, type /acp close and submit.
  2. Observe command processing.

Expected

  • /acp close executes directly without opening the action picker.

Actual

  • Action picker opens instead of executing (before fix).

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

What you personally verified (not just CI), and how:

  • Verified scenarios: Not run locally.
  • Edge cases checked: None.
  • What you did not verify: Discord interaction behavior in a live server.

Compatibility / Migration

  • Backward compatible? (Yes)
  • Config/env changes? (No)
  • Migration needed? (No)
  • If yes, exact upgrade steps:

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly: revert commit or re-register the /acp command with static choices.
  • Files/config to restore: src/discord/monitor/native-command.ts
  • Known bad symptoms reviewers should watch for: /acp autocompletion not showing choices.

Risks and Mitigations

  • Risk: Autocomplete might behave unexpectedly for /acp in some clients.
    • Mitigation: Limited to the /acp action arg; menu still appears when no action provided.

@openclaw-barnacle openclaw-barnacle bot added channel: discord Channel integration: discord size: XS labels Mar 3, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 3, 2026

Greptile Summary

This PR fixes a regression where typing /acp close (or any other inline action) in Discord would send action=null instead of the typed value, because the action option was registered with static choices. Discord resolves static-choice args strictly and ignores unmatched text, so any value not selected from the picker was dropped. The fix switches the /acp action option to autocomplete mode, which makes Discord pass the raw typed string through directly.

Key changes:

  • native-command.ts: adds a forceAutocomplete flag specifically for command.key === "acp" && arg.name === "action", inserting it into the shouldAutocomplete condition so the autocomplete handler is created and the static choices field is omitted for this option.
  • New test native-command.command-options.test.ts: verifies that buildDiscordCommandOptions produces an autocomplete function (not static choices) for the /acp action option.

Notes for reviewers:

  • The forceAutocomplete check is a hard-coded special case inside a generic function. Other commands with fewer than 25 static choices (e.g. /subagents action, /tts action) could face the same issue if Discord's behaviour applies to them; each would require another entry here or a refactor to a forceAutocomplete field on CommandArgDefinition.
  • The flag is named forceAutocomplete but is still gated behind resolvedChoices.length > 0, so it silently becomes a no-op if the acp action choices are ever removed — a potential future silent failure.

Confidence Score: 4/5

  • Safe to merge — small, targeted fix with a covering unit test and no runtime risk.
  • The root cause analysis is correct (static choices < 25, not a function → shouldAutocomplete = false), the fix is minimal and well-tested, and no existing behaviour changes for users who don't type an inline action. Score is 4 rather than 5 because the hard-coded command key check inside a generic function reduces maintainability, and the forceAutocomplete flag's dependency on resolvedChoices.length > 0 creates a silent failure mode if the choices are ever removed.
  • src/discord/monitor/native-command.ts lines 118–121 — the hard-coded "acp"/"action" check and forceAutocomplete semantics are worth a second look for long-term maintainability.

Last reviewed commit: a7d0868

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +118 to +121
const forceAutocomplete = command.key === "acp" && arg.name === "action";
const shouldAutocomplete =
resolvedChoices.length > 0 &&
(typeof arg.choices === "function" || resolvedChoices.length > 25);
(forceAutocomplete || typeof arg.choices === "function" || resolvedChoices.length > 25);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hard-coded command key in generic function

forceAutocomplete embeds command-specific knowledge ("acp" / "action") directly inside the generic buildDiscordCommandOptions function. Several other commands share the same structure — static choices arrays with fewer than 25 items (e.g. /subagents action, /tts action, /session action) — and would face the same inline-arg regression if Discord's behaviour applies to them too. Adding entries here each time would keep widening this special-case block.

A more extensible approach would be to add a forceAutocomplete?: boolean field to CommandArgDefinition and set it on the acp action arg at the definition site in commands-registry.data.ts, keeping this function generic:

// In commands-registry.data.ts, acp action arg:
{
  name: "action",
  description: "Action to run",
  type: "string",
  forceAutocomplete: true,
  choices: [ "spawn", "cancel", ... ],
}

// In native-command.ts:
const forceAutocomplete = arg.forceAutocomplete === true;

This is a design preference for now — the current approach works correctly for the targeted fix — but worth noting if similar issues arise on other commands.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/discord/monitor/native-command.ts
Line: 118-121

Comment:
**Hard-coded command key in generic function**

`forceAutocomplete` embeds command-specific knowledge (`"acp"` / `"action"`) directly inside the generic `buildDiscordCommandOptions` function. Several other commands share the same structure — static choices arrays with fewer than 25 items (e.g. `/subagents action`, `/tts action`, `/session action`) — and would face the same inline-arg regression if Discord's behaviour applies to them too. Adding entries here each time would keep widening this special-case block.

A more extensible approach would be to add a `forceAutocomplete?: boolean` field to `CommandArgDefinition` and set it on the `acp` `action` arg at the definition site in `commands-registry.data.ts`, keeping this function generic:

```typescript
// In commands-registry.data.ts, acp action arg:
{
  name: "action",
  description: "Action to run",
  type: "string",
  forceAutocomplete: true,
  choices: [ "spawn", "cancel", ... ],
}

// In native-command.ts:
const forceAutocomplete = arg.forceAutocomplete === true;
```

This is a design preference for now — the current approach works correctly for the targeted fix — but worth noting if similar issues arise on other commands.

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines 119 to +121
const shouldAutocomplete =
resolvedChoices.length > 0 &&
(typeof arg.choices === "function" || resolvedChoices.length > 25);
(forceAutocomplete || typeof arg.choices === "function" || resolvedChoices.length > 25);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

forceAutocomplete is silently suppressed when choices are absent

Despite the name implying an unconditional override, forceAutocomplete has no effect when resolvedChoices.length === 0 because of the outer && guard:

const shouldAutocomplete =
  resolvedChoices.length > 0 &&   // ← still blocks when choices = []
  (forceAutocomplete || ...);

Currently this is safe — the acp action arg always carries 15 static choices — but if someone ever refactored those choices away, the flag would silently stop working with no error or warning. Consider restructuring to make the intent explicit:

Suggested change
const shouldAutocomplete =
resolvedChoices.length > 0 &&
(typeof arg.choices === "function" || resolvedChoices.length > 25);
(forceAutocomplete || typeof arg.choices === "function" || resolvedChoices.length > 25);
const shouldAutocomplete =
forceAutocomplete
? resolvedChoices.length > 0 // need choices to respond with
: resolvedChoices.length > 0 && (typeof arg.choices === "function" || resolvedChoices.length > 25);

This makes the dependency on choices explicit for the forced case while keeping the same runtime semantics.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/discord/monitor/native-command.ts
Line: 119-121

Comment:
**`forceAutocomplete` is silently suppressed when choices are absent**

Despite the name implying an unconditional override, `forceAutocomplete` has no effect when `resolvedChoices.length === 0` because of the outer `&&` guard:

```typescript
const shouldAutocomplete =
  resolvedChoices.length > 0 &&   // ← still blocks when choices = []
  (forceAutocomplete || ...);
```

Currently this is safe — the `acp action` arg always carries 15 static choices — but if someone ever refactored those choices away, the flag would silently stop working with no error or warning. Consider restructuring to make the intent explicit:

```suggestion
    const shouldAutocomplete =
      forceAutocomplete
        ? resolvedChoices.length > 0  // need choices to respond with
        : resolvedChoices.length > 0 && (typeof arg.choices === "function" || resolvedChoices.length > 25);
```

This makes the dependency on choices explicit for the forced case while keeping the same runtime semantics.

How can I resolve this? If you propose a fix, please make it concise.

@thewilloftheshadow
Copy link
Copy Markdown
Member

Superseded by #33136, which consolidates the /acp inline action autocomplete fix plus the bound-thread bot system message guard. Closing in favor of that PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: discord Channel integration: discord size: XS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: /acp slash command ignores inline arg, shows menu anyway

2 participants