openai/codex · error · QueueServiceError
queued submission payload is invalid: {0}
Error message
queued submission payload is invalid: {0} What it means
Wraps serde_json::Error via #[from] (codex-rs/ext/queue/src/service.rs:50-51). It has two directions: serializing a TurnInput when enqueueing or updating (service.rs:270 and 321, practically unreachable for well-formed values) and deserializing a stored payload back into TurnInput in queued_item_from_record (service.rs:560-566). In practice it fires on the read side: a queue record whose payload no longer matches the TurnInput schema, typically written by an older or newer binary. The dispatcher itself treats such records as garbage, discarding them with a warning (service.rs:419-426), but list(), update(), and start() propagate the error.
Source
Thrown at codex-rs/ext/queue/src/service.rs:50
use codex_thread_store::ThreadStoreError;
use thiserror::Error;
use tokio::sync::Mutex;
use tokio::sync::OwnedMutexGuard;
use tokio::sync::broadcast::error::TryRecvError;
use uuid::Uuid;
/// 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>,View on GitHub (pinned to 339751715c)
Solutions
- Run one version at a time against a given state database so writer and reader agree on the schema.
- Drain or delete stale queued items; the automatic dispatcher already discards invalid ones when the thread goes idle, and queue.delete(thread_id, item_id) removes specific rows.
- Read the serde error message; it names the field that failed to deserialize.
- For queues that must survive upgrades, add a migration or clear the thread queue table.
Example fix
// before
let items = service.list(thread_id).await?; // fails if any stored payload is stale
// after - isolate and drop unreadable rows (same policy as the dispatcher)
for record in store.list_page(thread_id, 0, MAX_QUEUE_ITEMS).await? {
if serde_json::from_str::<TurnInput>(&record.payload).is_err() {
tracing::warn!(id = %record.id, "dropping unreadable queued item");
store.delete(thread_id, record.id).await?;
}
}
let items = service.list(thread_id).await?; Defensive patterns
Strategy: fallback
Validate before calling
// Rust - probe a stored payload before trusting it
fn payload_parses(payload: &str) -> bool {
serde_json::from_str::<TurnInput>(payload).is_ok()
} Try / catch
match service.list(thread_id).await {
Ok(items) => { /* ... */ }
Err(QueueServiceError::InvalidPayload(serde_err)) => {
// fall back to store-level listing and drop rows that do not parse
// (the dispatcher uses the same warn-and-delete policy, service.rs:419-426)
tracing::warn!(%serde_err, "dropping unreadable queue rows");
}
Err(e) => return Err(e),
} Prevention
- Run a single codex version against each state database.
- After upgrades with items still queued, expect the dispatcher to discard old-shape rows.
- Never hand-edit queue payloads.
- Wrap list() so one bad row degrades gracefully instead of failing the whole queue view.
When it happens
Trigger: Calling list(), list_page(), update(), or start() while the store contains a payload that fails to deserialize as TurnInput; version skew between the writer and reader of the queue table; hand-edited or migrated queue rows.
Common situations: Upgrading codex while old queued messages exist and the TurnInput shape changed; two different versions running against the same state database; test fixtures with hand-written payloads.
Related errors
- failed to parse app config for selected plugin `{plugin_id}`
- failed to parse MCP config for selected plugin `{plugin_id}`
- queue storage failed: {0}
- local queued attachment is invalid: {0}
- pid-managed app-server shutdown is unsupported on this platf
AI-assisted analysis of openai/codex@339751715c (2026-08-25).
Data as JSON: /api/errors/402d2ce90f7914b9.
Report an issue: GitHub.