# When a reply throws

Discord tracks what state an interaction is in, and that decides which reply methods still work. Covers the states, the methods each one allows, and three errors a reply call throws.

seedcord checks the method against the interaction's current state before it calls Discord. An illegal call throws without reaching the API, where Discord would reject it with an error that doesn't say which method to use. The message says which method you called, why it can't run, and which method works in that state.

```txt output
reply() was called when this interaction was already replied to.
Use followUp() for a new message or edit() to rewrite the reply. (route slash:ban)
```

The error's `cause` is a second error, such as `reply() acknowledged this interaction`, whose stack points at the line that answered first. One throw gives you both lines of a double ack, including a first one inside a helper in another file.

<Image src="/double-ack-stack.webp" alt="The seedcord dev terminal showing SeedcordError 1501 for a second reply() at Ban.ts line 7, then Caused by AckTrace, reply() acknowledged this interaction, at Ban.ts line 6" width="2176" height="644" frame="thin" />

The top stack ends at `Ban.ts:7`, the second `reply()`. The `AckTrace` under it ends at `Ban.ts:6`, the reply that went out first. Your terminal prints both stacks when [`errorStack`](/replying/error-behavior) is on.

## The states

{/* prettier-ignore-start */}

| state             | how you get there                       |
| ----------------- | --------------------------------------- |
| `unacked`         | nothing sent yet                        |
| `deferred-reply`  | `defer()`                               |
| `deferred-update` | `deferUpdate()`                         |
| `replied`         | `reply()`, `update()`, or `showModal()` |

{/* prettier-ignore-end */}

[`update()`](https://docs.seedcord.org/packages/gateway/latest/component-handler#update) and [`deferUpdate()`](https://docs.seedcord.org/packages/gateway/latest/component-handler#defer-update) are defined on component and modal handlers, covered in the Components tab. [`showModal()`](https://docs.seedcord.org/packages/gateway/latest/interaction-handler#show-modal) is on every command and component handler. A modal handler doesn't have it, since Discord doesn't let a modal open another modal.

## Which method each state allows

{/* prettier-ignore-start */}

| state             | legal                                                          | throws                                                 |
| ----------------- | -------------------------------------------------------------- | ------------------------------------------------------ |
| `unacked`         | `reply`, `defer`, `deferUpdate`, `update`, `showModal`, `send` | `followUp`, `edit`, `delete`                           |
| `deferred-reply`  | `edit`, `followUp`, `delete`, `send`                           | `reply`, `defer`, `deferUpdate`, `update`, `showModal` |
| `deferred-update` | `update`, `edit`, `followUp`, `delete`, `send`                 | `reply`, `defer`, `deferUpdate`, `showModal`           |
| `replied`         | `edit`, `followUp`, `delete`, `send`                           | `reply`, `defer`, `deferUpdate`, `update`, `showModal` |

{/* prettier-ignore-end */}

[`send()`](https://docs.seedcord.org/packages/gateway/latest/repliable-handler#send) runs from every state, since it picks whichever method the state allows. [Follow-ups and edits](/replying/more-messages) shows the mapping.

## A component that fails to serialize

seedcord calls `toJSON()` on every component in your reply, and discord.js validates the builder there. Its own throw is a nested shapeshift error, which doesn't say which entry in `components` failed. seedcord catches it and rewrites it as one line with the class, its index in `components`, and what went wrong.

```ts title="src/handlers/Ban.ts"
import {
    BuilderComponent,
    SlashHandler,
    SlashRoute
} from '@seedcord/gateway';

class BanText extends BuilderComponent<'text_display'> {
    constructor() {
        super('text_display');
    }
}

@SlashRoute('ban')
export class Ban extends SlashHandler<'ban'> {
    public async execute(): Promise<void> {
        await this.reply({
            components: [new BanText().component]
        });
    }
}
```

```txt output
TextDisplayBuilder at components[0] failed to serialize: need a
string, got nothing. (route slash:ban)
```

`BanText` above never calls `setContent`, so its builder never got a string to send. The detail after the colon reads `need <the rule>, got <your value>`. A bound uses `at most`, `at least`, `fewer than`, or `more than`, and a rule carrying two bounds joins them with `and`. A failure on a named field puts that field and a colon first, as with `url:` on a file component's link.

The `got` half describes the value you passed.

{/* prettier-ignore-start */}

| what you passed              | how it reads        |
| ---------------------------- | ------------------- |
| `undefined`                  | `nothing`           |
| `null`                       | `null`              |
| an empty string              | `an empty string`   |
| a string up to 40 characters | the string, quoted  |
| a longer string              | the character count |
| an empty array               | `an empty list`     |
| a longer array               | the item count      |
| a number, bigint, or boolean | the value itself    |
| any other object             | its class name      |

{/* prettier-ignore-end */}

A discord.js throw that never reaches its validator keeps its own message, like "Non-premium buttons must have a label and/or an emoji."

The original throw stays on the error as its `cause`, so you can still read the nested shapeshift error underneath.

## The interaction callback returned no message

`reply()` and `update()` read the created message out of Discord's callback response.

```txt output
The interaction callback for reply() returned no message.
(route slash:ban)
```

Your handler can't cause this throw. The callback carries that message on every documented path, so this one fires when Discord's response doesn't include it.

## update() on a modal a command opened

`update()` rewrites the message that a component came from. A modal that your command opened does not carry one.

```txt output
update() was called on a modal opened from a command, which has no
source message.
Use reply() or defer() instead. (route slash:ban)
```
