{"record":{"id":"0d893a9270195df0","repo":"openai/codex","slug":"line-offset-must-be-a-1-indexed-line-number","errorCode":null,"errorMessage":"line_offset must be a 1-indexed line number","messagePattern":"line_offset must be a 1-indexed line number","errorType":"validation","errorClass":"MemoriesBackendError","httpStatus":null,"severity":"error","filePath":"codex-rs/ext/memories/src/backend.rs","lineNumber":149,"sourceCode":"    pub content: String,\n    pub matched_queries: Vec<String>,\n}\n\n#[derive(Debug, thiserror::Error)]\npub enum MemoriesBackendError {\n    #[error(\"filename '{filename}' {reason}\")]\n    InvalidFilename { filename: String, reason: String },\n    #[error(\"ad-hoc note must not be empty\")]\n    EmptyAdHocNote,\n    #[error(\"ad-hoc note '{filename}' already exists\")]\n    AdHocNoteAlreadyExists { filename: String },\n    #[error(\"path '{path}' {reason}\")]\n    InvalidPath { path: String, reason: String },\n    #[error(\"cursor '{cursor}' {reason}\")]\n    InvalidCursor { cursor: String, reason: String },\n    #[error(\"path '{path}' was not found\")]\n    NotFound { path: String },\n    #[error(\"line_offset must be a 1-indexed line number\")]\n    InvalidLineOffset,\n    #[error(\"max_lines must be a positive integer\")]\n    InvalidMaxLines,\n    #[error(\"line_offset exceeds file length\")]\n    LineOffsetExceedsFileLength,\n    #[error(\"path '{path}' is not a file\")]\n    NotFile { path: String },\n    #[error(\"queries must not be empty or contain empty strings\")]\n    EmptyQuery,\n    #[error(\"all_within_lines.line_count must be a positive integer\")]\n    InvalidMatchWindow,\n    #[error(\"I/O error while reading memories: {0}\")]\n    Io(#[from] std::io::Error),\n}\n\nimpl MemoriesBackendError {\n    pub fn invalid_filename(filename: impl Into<String>, reason: impl Into<String>) -> Self {\n        Self::InvalidFilename {","sourceCodeStart":131,"sourceCodeEnd":167,"githubUrl":"https://github.com/openai/codex/blob/339751715c64496cb86246bfb3935f40e309dd3d/codex-rs/ext/memories/src/backend.rs#L131-L167","documentation":"Thrown by the memories read path (codex-rs/ext/memories/src/local/read.rs:16-18) when ReadMemoryRequest.line_offset is 0. Memory files are addressed with 1-indexed lines, where line 1 is the first line, so 0 is never a valid start and the request is rejected before any filesystem access. The tool-level schema (codex-rs/ext/memories/src/tools/read.rs:29) already declares schemars range(min = 1) and defaults to 1, so this variant is reached mainly by code calling the MemoriesBackend trait directly.","triggerScenarios":"Calling backend.read with ReadMemoryRequest { line_offset: 0, .. }; a usize field that defaulted to 0; feeding a 0-based index from an editor, cursor, or grep-style output straight into line_offset; a custom ToolExecutor that parses args without the ReadArgs schema constraint.","commonSituations":"Porting editor or grep-style code that numbers lines from 0; tests constructing ReadMemoryRequest by hand; a UI passing its 0-based selection start; refactors that renamed an index field into line_offset without adding 1.","solutions":["Pass line_offset >= 1 (the first line of the file is 1).","Translate 0-based indexes with line_offset = index + 1.","Default the value to 1 at your boundary, mirroring tools/read.rs (unwrap_or(1)).","Keep a schemars range(min = 1) constraint on any JSON argument so invalid values fail schema validation with a clearer message."],"exampleFix":"// before\nlet resp = backend.read(ReadMemoryRequest {\n    path,\n    line_offset: cursor_index, // 0-based index from the editor\n    max_lines: Some(200),\n    max_tokens: 0,\n}).await?;\n\n// after\nlet resp = backend.read(ReadMemoryRequest {\n    path,\n    line_offset: cursor_index + 1, // convert 0-based index to 1-indexed line\n    max_lines: Some(200),\n    max_tokens: 0,\n}).await?;","handlingStrategy":"validation","validationCode":"// Rust - validate before calling MemoriesBackend::read\nfn valid_line_offset(offset: usize) -> bool {\n    offset >= 1 // lines are 1-indexed\n}\n\n// convert a 0-based index safely at the boundary\nlet line_offset = raw_index.saturating_add(1).max(1);","typeGuard":null,"tryCatchPattern":"match backend.read(request).await {\n    Ok(response) => { /* ... */ }\n    Err(MemoriesBackendError::InvalidLineOffset) => {\n        // caller bug: 0 was passed; correct to 1 and retry once\n    }\n    Err(other) => return Err(other),\n}","preventionTips":["Treat memory line numbers as 1-indexed end to end; convert 0-based indexes with +1 at the boundary.","Default line_offset to 1 the way tools/read.rs does.","Keep the schemars range(min = 1) constraint on JSON args so bad values are rejected at schema validation."],"tags":["rust","codex","memories","validation","off-by-one","line-offset"],"backgroundTag":"off-by-one-index","analyzedSha":"339751715c64496cb86246bfb3935f40e309dd3d","analyzedAt":"2026-08-25T05:35:09.876Z","schemaVersion":2},"datasetVersion":"2026-08-25T06:17:31.827Z"}