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
- 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
- Guard the call site: disable or skip the send/queue action while the composer is empty
- 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
- Disable queue/send UI actions on empty composers
- Keep a single constructor for queueable turns that asserts the UserInput shape
- Never feed system-generated or programmatic turns into the user-message queue
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
- queued user input exceeds the maximum length of {MAX_USER_IN
- queue storage failed: {0}
- queued submission payload is invalid: {0}
- local queued attachment is invalid: {0}
- Core failed to submit queued user message: {0}
AI-assisted analysis of openai/codex@339751715c (2026-08-25).
Data as JSON: /api/errors/c3d99cf953cd4221.
Report an issue: GitHub.