openai/codex · error · QueueServiceError

only user input can be added to the user-message queue

Error message

only user input can be added to the user-message queue

What it means

QueueServiceError::InvalidInput is returned by prepare_queued_user_input (codex-rs/ext/queue/src/service.rs:484-490) when the TurnInput handed to enqueue()/update() is not the UserInput variant, or when its content Vec is empty; start() also returns it if a stored queue record is somehow not UserInput (service.rs:387-389). The user-message queue is deliberately restricted - only real, non-empty user messages may be queued, because each queued item is replayed verbatim as a user turn when the thread goes idle.

Source

Thrown at codex-rs/ext/queue/src/service.rs:56

/// One user message waiting to start on its thread.
#[derive(Clone, Debug, PartialEq)]
pub struct QueuedItem {
    pub id: String,
    pub input: TurnInput,
}

#[derive(Debug, Error)]
pub enum QueueServiceError {
    #[error("queue storage failed: {0}")]
    Storage(#[from] ThreadStoreError),
    #[error("queued submission payload is invalid: {0}")]
    InvalidPayload(#[from] serde_json::Error),
    #[error("local queued attachment is invalid: {0}")]
    InvalidAttachment(#[from] std::io::Error),
    #[error("Core failed to submit queued user message: {0}")]
    CoreSubmissionError(#[from] CodexErr),
    #[error("only user input can be added to the user-message queue")]
    InvalidInput,
    #[error(
        "queued user input exceeds the maximum length of {MAX_USER_INPUT_TEXT_CHARS} characters ({actual_chars} provided)"
    )]
    InputTooLarge { actual_chars: usize },
}

#[derive(Clone)]
pub struct QueuedItemService {
    queue: Arc<dyn QueueStore>,
    thread_manager: Weak<ThreadManager>,
    event_sink: Arc<dyn ExtensionEventSink>,
    dispatch_locks: Arc<StdMutex<HashMap<ThreadId, Weak<Mutex<()>>>>>,
    resumed_threads: Arc<StdMutex<HashSet<ThreadId>>>,
}

impl QueuedItemService {
    pub fn new(

View on GitHub (pinned to 339751715c)

Solutions

  1. Pass only TurnInput::UserInput with a non-empty content Vec to enqueue/update; route programmatic turns through the normal submit path instead of the queue
  2. Guard the call site: disable or skip the send/queue action while the composer is empty
  3. If hit from start(), inspect the stored payload and delete the malformed item via delete(thread_id, item_id); the auto-dispatch path already discards such rows with a 'discarding non-user queued input' warning

Example fix

// before
let item = queue.enqueue(thread_id, input).await?; // InvalidInput for non-user or empty turns

// after
if !matches!(&input, TurnInput::UserInput { content, .. } if !content.is_empty()) {
    return Ok(None); // nothing queueable - require real user text
}
let item = queue.enqueue(thread_id, input).await?;
Defensive patterns

Strategy: validation

Validate before calling

fn is_queueable(input: &TurnInput) -> bool {
    matches!(input, TurnInput::UserInput { content, .. } if !content.is_empty())
}
// before enqueue/update:
if !is_queueable(&input) { return Ok(None); }

Type guard

fn as_queueable_user_input(input: TurnInput) -> Option<TurnInput> {
    match input {
        input @ TurnInput::UserInput { ref content, .. } if !content.is_empty() => Some(input),
        _ => None,
    }
}

Try / catch

match err {
    QueueServiceError::InvalidInput => { /* surface: type a message first / unsupported turn type */ }
    _ => { /* other handling */ }
}

Prevention

When it happens

Trigger: enqueue(thread_id, input) or update(...) where input is a TurnInput variant other than UserInput (programmatic or environment-context turns); TurnInput::UserInput with content = vec![] (empty composer submit); start() on a queue row whose stored payload was tampered with or written by an incompatible build.

Common situations: A send-button handler that still fires with an empty composer; reusing one submission helper for both user chat and system-injected turns; unit tests constructing TurnInput::UserInput with an empty content Vec.

Related errors


AI-assisted analysis of openai/codex@339751715c (2026-08-25). Data as JSON: /api/errors/c3d99cf953cd4221. Report an issue: GitHub.