Incident brief
Negative window frame offset
A window frame's PRECEDING/FOLLOWING offset must be non-negative; a negative frame bound raises this data exception.
What lands in your log
ERROR: frame starting offset must not be negative
In 10 seconds
- What triggers it
- Define a window frame such as ROWS BETWEEN <n> PRECEDING with a negative <n>.
- Fix
- Use a non-negative frame offset.
- Proof
- Reproduced on PostgreSQL 18.4 → A single windowed SELECT reproduces SQLSTATE 22013; the frame offset is rejected before any aggregate is produced.
Fix
What to do right now
Application-level steps for this error.
- Use a non-negative frame offset.
- Clamp any computed frame bound so it can never go below zero.
-- Window frame offsets must be zero or positive.
SELECT sum(g) OVER (ORDER BY g ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) FROM generate_series(1,3) g;For this error
See this error live on the server
Run these against the affected instance to confirm the diagnosis before you act.
Negative window frame offset (22013) is statement-local: PostgreSQL does not retain the rejected value after the statement ends. Capture the exact bound value and statement position in application/server logs, then run this targeted validation before retrying.
Validate the window-frame offset
Validate the specific value/shape that can raise this SQLSTATE.
SELECT frame_offset,
frame_offset < 0 AS would_raise_22013
FROM (VALUES (-1)) v(frame_offset);Why it happens
What PostgreSQL is telling you
The mechanism behind the error, grounded in the official manual, not paraphrased.
PostgreSQL 18 Documentation, Appendix A. PostgreSQL Error Codes (Table A.1, Class 22, Data Exception)
22013 → invalid_preceding_or_following_sizeRead the full section on postgresql.org →
Using a negative frame offset
Frame offsets count rows and cannot be negative, so a negative PRECEDING bound raises 'frame starting offset must not be negative'.The session continues normally
The frame-offset check failed on an ordinary, non-transactional statement, so the session picks back up normally on its next query.Reproduce & verify
A real, single-session PostgreSQL reproduction
A literal transcript of SQL run against a live PostgreSQL instance in an isolated lab. The commands below are exactly what was executed.
- 1Define a window frame such as ROWS BETWEEN <n> PRECEDING with a negative <n>.
- 2PostgreSQL validates the frame offset before computing the aggregate.
- 3A negative offset aborts the statement with SQLSTATE 22013 and 'frame starting offset must not be negative'.
A moving-sum used ROWS BETWEEN k PRECEDING where k was computed per query, and a negative k aborted the running total.
-- The error is self-contained in one statement; no schema is required.
SELECT 'no schema needed' AS setup_note;SELECT sum(g) OVER (ORDER BY g ROWS BETWEEN -1 PRECEDING AND CURRENT ROW) FROM generate_series(1,3) g;SELECT 'ok' AS session_after_error;What PostgreSQL actually returned
setup_note
------------------
no schema needed
(1 row)ERROR: frame starting offset must not be negative session_after_error
---------------------
ok
(1 row)The same moving aggregate works once the frame offset is non-negative.
Without this
Before: a negative frame offset aborts
With this, tested
After: ROWS BETWEEN 1 PRECEDING computes
- A second operational test: exact SQL, raw output, measured result, and engineer notes
Card required. Cancel before day 7 and you are not charged.
Connected
Everything this error touches
Every page this SQLSTATE connects to: the concept that explains it, the runbooks that fix it, the parameters you tune to prevent it, and the sibling errors it travels with. All real cross-references. Jump straight in, or open the full interactive map.
Verification
- Last verified
- 2026-07-24 (isolated lab, PostgreSQL 18.4)
- Verification scope
- Verified against PostgreSQL 18.4 in an isolated lab environment
- Audit status
- reviewed
Went further?
Pro unlocks the second lab proof
Free page stops the bleeding. Pro adds the operational test, SQLSTATE audit, and deeper evidence, same error, more certainty.