{"record":{"id":"c3d99cf953cd4221","repo":"openai/codex","slug":"only-user-input-can-be-added-to-the-user-message-q","errorCode":null,"errorMessage":"only user input can be added to the user-message queue","messagePattern":"only user input can be added to the user-message queue","errorType":"validation","errorClass":"QueueServiceError","httpStatus":null,"severity":"error","filePath":"codex-rs/ext/queue/src/service.rs","lineNumber":56,"sourceCode":"\n/// One user message waiting to start on its thread.\n#[derive(Clone, Debug, PartialEq)]\npub struct QueuedItem {\n    pub id: String,\n    pub input: TurnInput,\n}\n\n#[derive(Debug, Error)]\npub enum QueueServiceError {\n    #[error(\"queue storage failed: {0}\")]\n    Storage(#[from] ThreadStoreError),\n    #[error(\"queued submission payload is invalid: {0}\")]\n    InvalidPayload(#[from] serde_json::Error),\n    #[error(\"local queued attachment is invalid: {0}\")]\n    InvalidAttachment(#[from] std::io::Error),\n    #[error(\"Core failed to submit queued user message: {0}\")]\n    CoreSubmissionError(#[from] CodexErr),\n    #[error(\"only user input can be added to the user-message queue\")]\n    InvalidInput,\n    #[error(\n        \"queued user input exceeds the maximum length of {MAX_USER_INPUT_TEXT_CHARS} characters ({actual_chars} provided)\"\n    )]\n    InputTooLarge { actual_chars: usize },\n}\n\n#[derive(Clone)]\npub struct QueuedItemService {\n    queue: Arc<dyn QueueStore>,\n    thread_manager: Weak<ThreadManager>,\n    event_sink: Arc<dyn ExtensionEventSink>,\n    dispatch_locks: Arc<StdMutex<HashMap<ThreadId, Weak<Mutex<()>>>>>,\n    resumed_threads: Arc<StdMutex<HashSet<ThreadId>>>,\n}\n\nimpl QueuedItemService {\n    pub fn new(","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/openai/codex/blob/339751715c64496cb86246bfb3935f40e309dd3d/codex-rs/ext/queue/src/service.rs#L38-L74","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nlet item = queue.enqueue(thread_id, input).await?; // InvalidInput for non-user or empty turns\n\n// after\nif !matches!(&input, TurnInput::UserInput { content, .. } if !content.is_empty()) {\n    return Ok(None); // nothing queueable - require real user text\n}\nlet item = queue.enqueue(thread_id, input).await?;","handlingStrategy":"validation","validationCode":"fn is_queueable(input: &TurnInput) -> bool {\n    matches!(input, TurnInput::UserInput { content, .. } if !content.is_empty())\n}\n// before enqueue/update:\nif !is_queueable(&input) { return Ok(None); }","typeGuard":"fn as_queueable_user_input(input: TurnInput) -> Option<TurnInput> {\n    match input {\n        input @ TurnInput::UserInput { ref content, .. } if !content.is_empty() => Some(input),\n        _ => None,\n    }\n}","tryCatchPattern":"match err {\n    QueueServiceError::InvalidInput => { /* surface: type a message first / unsupported turn type */ }\n    _ => { /* other handling */ }\n}","preventionTips":["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"],"tags":["queue","input-validation","turn-input","rust"],"backgroundTag":"invalid-input-type","analyzedSha":"339751715c64496cb86246bfb3935f40e309dd3d","analyzedAt":"2026-08-25T05:35:09.876Z","schemaVersion":2},"datasetVersion":"2026-08-25T06:17:31.827Z"}