{"record":{"id":"4fbe7165d6e5ec7f","repo":"facebook/flow","slug":"invalid-position-line-column","errorCode":null,"errorMessage":"Invalid position: {{line: {}; column: {}}}","messagePattern":"Invalid position: (.+?); column: (.+?)\\}\\}","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"rust_port/crates/flow_server_utils/src/file_content.rs","lineNumber":85,"sourceCode":"        }\n        if acc.0.is_some() && acc.1.is_none() && is_target(queries.1, line, column) {\n            acc.1 = Some(offset);\n            continue;\n        }\n        let c = get_char(content, offset);\n        if c == b'\\n' {\n            line += 1;\n            column = 1;\n            offset += 1;\n        } else {\n            column += 1;\n            offset += get_char_length(c);\n        }\n    }\n}\n\nfn invalid_position(p: &Position) -> ! {\n    panic!(\n        \"Invalid position: {{line: {}; column: {}}}\",\n        p.line, p.column\n    )\n}\n\npub fn get_offsets(content: &str, queries: (&Position, &Position)) -> (usize, usize) {\n    match get_offsets_rec(content.as_bytes(), queries, 1, 1, 0, (None, None)) {\n        (Some(r1), Some(r2)) => (r1, r2),\n        (None, _) => invalid_position(queries.0),\n        (_, None) => invalid_position(queries.1),\n    }\n}\n\npub fn get_offset(content: &str, position: &Position) -> usize {\n    get_offsets(content, (position, position)).0\n}\n\npub fn offset_to_position(content: &str, offset: usize) -> Position {","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/facebook/flow/blob/f88ac94bcf6992f5d5a158854d94613ebb92c6e6/rust_port/crates/flow_server_utils/src/file_content.rs#L67-L103","documentation":"file_content::get_offsets() scans a file's bytes to convert two 1-based Positions (line, column) into byte offsets. If a position is never reached while scanning — the line or column is past the end of the content — invalid_position() panics with the offending line/column. This helper backs position-based server queries, so bad positions from any client surface here.","triggerScenarios":"A client sends a position beyond EOF (line greater than the file's line count, or column greater than that line's length); positions computed against a different version of the content than the server holds (race between didChange and the query); 0-based line/column values from a client that should be 1-based.","commonSituations":"Editor queries racing file saves; clients being off-by-one on the line/column base; queries positioned one past the end of the last line; CRLF/BOM differences making the client's column math disagree with the server's bytes; stale buffers after external file changes (git checkout, formatter).","solutions":["Re-fetch the content and compute positions against exactly that text, in the same synchronization frame as the query.","Clamp before sending: line to the number of lines, column to the target line's length + 1.","Send 1-based line and column values.","If you fork: make get_offsets return Result and translate out-of-range positions into a clean error response instead of a panic."],"exampleFix":"// before: position computed from a stale buffer\nconst pos = oldBuffer.positionAt(cursorOffset);\nsendQuery({ line: pos.line, column: pos.column });\n\n// after: clamp against the content the server holds\nconst lines = currentContent.split('\\n');\nconst line = Math.min(reqLine, lines.length);\nconst column = Math.min(reqColumn, lines[line - 1].length + 1);\nsendQuery({ line, column });","handlingStrategy":"validation","validationCode":"function positionInRange(content, line, column) {\n  const lines = content.split('\\n');\n  return line >= 1 && line <= lines.length &&\n         column >= 1 && column <= lines[line - 1].length + 1;\n}\n\n// before sending any position query\nif (!positionInRange(serverContent, line, column)) {\n  const clamped = clampPosition(serverContent, line, column);\n  ({ line, column } = clamped);\n}","typeGuard":"function positionInRange(content: string, line: number, column: number): boolean {\n  const lines = content.split('\\n');\n  return Number.isInteger(line) && Number.isInteger(column) &&\n         line >= 1 && line <= lines.length &&\n         column >= 1 && column <= lines[line - 1].length + 1;\n}","tryCatchPattern":null,"preventionTips":["Fetch content and send positions in the same synchronization frame so they cannot disagree.","Send 1-based line/column; convert client coordinates explicitly at the boundary.","Clamp end-of-file queries to the last line's length + 1 instead of one-past positions.","Watch for CRLF/BOM: normalize or compute columns from the same bytes the server holds."],"tags":["positions","lsp","off-by-one","validation","offsets","race-condition"],"backgroundTag":"position-out-of-range","analyzedSha":"f88ac94bcf6992f5d5a158854d94613ebb92c6e6","analyzedAt":"2026-08-20T10:41:37.992Z","contentChangedAt":"2026-08-20T10:41:37.992Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}