24000 — invalid_cursor_state
24000 means the cursor or portal state does not satisfy the operation. The selected CURRENT OF case declares a valid cursor but tries to update before FETCH has positioned it, producing “cursor ... is not positioned on a row”.
English manual coverage begins with PostgreSQL 8.1. Source history and runtime observations have their own recorded version boundaries.
Reading PG 18·Select a recorded version
- aliases
- Unknown
- class name
- Invalid Cursor State
- condition name
- invalid_cursor_state
- lang
- en
- sqlstate
- 24000
Definition PG 18
24000 — invalid_cursor_state
At a glance {#at-a-glance}
24000 means the cursor or portal state does not satisfy the operation. The selected CURRENT OF case declares a valid cursor but tries to update before FETCH has positioned it, producing “cursor ... is not positioned on a row”.
Meaning {#meaning}
WHERE CURRENT OF needs a cursor declared for an updatable query, with a current row selected by FETCH. Declaration alone is not positioning. PostgreSQL uses 24000 for other cursor states too, including non-SELECT, held, non-updatable, or missing FOR UPDATE/SHARE references.
Diagnosis {#diagnosis}
Record the cursor name and operation, then inspect declaration, transaction lifetime, FETCH direction, and whether the cursor is updatable. In an explicit transaction the selected error leaves INERROR; ROLLBACK restores IDLE. The selected repair updates by primary key, so it verifies transaction recovery and a safe direct repair, not a second DECLARE/FETCH path.
Response {#response}
Position the cursor with FETCH before CURRENT OF, or use a deterministic key-based update when that is the intended operation. Keep cursor lifetime and transaction boundaries explicit, and roll back a failed transaction before issuing the repair.
Observed diagnostics {#messages}
18.6 (Homebrew) / latest:SQLSTATE 24000; primary cursor "item_cursor" is not positioned on a row; after_error INERROR; after_rollback IDLE.
10.21 (Debian 10.21-1.pgdg90+1) / pg10:SQLSTATE 24000; primary cursor "item_cursor" is not positioned on a row; after_error INERROR; after_rollback IDLE.
Representative case {#case}
The runner reads these statements from verify/cases/24000/snippets.json (SHA-256 cafca57825378362df0527384614bf658bf86cd3101e1eb520ea0641819f2789) and qualifies the temporary table names; the complete setup, assertions, and cleanup are in the case export.
-- create
CREATE TABLE items(id integer PRIMARY KEY, note text NOT NULL);
-- seed
INSERT INTO items VALUES (1, 'seed');
-- begin
BEGIN;
-- declare
DECLARE item_cursor CURSOR FOR SELECT id FROM items FOR UPDATE;
-- trigger
UPDATE items SET note = 'bad' WHERE CURRENT OF item_cursor;
-- rollback
ROLLBACK;
-- repair
UPDATE items SET note = 'repaired' WHERE id = 1;
-- verify
SELECT id, note FROM items;
The SQLSTATE, diagnostic, transaction-state, and repair assertions for this excerpt are produced from the shared registry; structured evidence · case export.
Authored evidence IDs: identity, current-of, runtime. Selected runtime records: runtime.24000-batch1-latest2-20260909.latest, runtime.24000-batch1-pg10-20260909.pg10.
Versions {#versions}
The locked catalogue observes the condition by 7.4 and in all listed formal snapshots. The selected before-FETCH path passes on 18.6 and 10.21; other cursor-state templates require separate cases.
Related {#related}
Compare 25001 active SQL transaction, 25P02 failed SQL transaction, and 34000 invalid cursor name.
Sources {#sources}
src.errcodes.18.6(SHA-2566e8de346643ba84aa3c9c6a73360acfc7b2dfb89162c06c08ce9bf5bcd5bbcba)src.calls.REL_18_6(SHA-2569ee8a0e81d8f0825c5c1ae45583439859a26e602bdd4ce2f2a62aa278867ccbf)src.execCurrent.18.6(SHA-25645c8e48b3a5dc46f1014482dc88e0a26c25c3a4c403372112474d9171253d8a9)doc.declare.18.6(SHA-256f1d72befb9a989aa32123560aea784a8bccfcb3d3930804a101d8245e5a3cc95) · official documentationdoc.update.18.6(SHA-25647dd724cd724bd77478215a4853aa1f985996a0e5bba9b2451a1ad1ebb6e51fc) · official documentation
Compare versions
SQLSTATE evidence and recorded runtime versions
cases · current_of_before_fetch
{
"assertions": [
"SQLSTATE is 24000",
"The connection recovers after rollback",
"A direct update succeeds afterward"
],
"case_id": "current_of_before_fetch",
"cleanup": "Drop the case schema with an owner connection.",
"has_snippet": true,
"position": 0,
"preconditions": [
"A cursor is declared for a SELECT ... FOR UPDATE",
"The cursor has not fetched a row"
],
"repair": "Fetch or otherwise position the cursor before CURRENT OF, or use a key-based update whose concurrency semantics are explicit.",
"trigger": "Use WHERE CURRENT OF before the cursor is positioned on a row.",
"versions": [
"10",
"18"
]
}claims · identity
{
"claim_id": "identity",
"limits": "The class has several cursor/portal states; the observed message is one path.",
"method": "Read the fixed condition row.",
"position": 0,
"runtime": [],
"sources": [
"src.errcodes.18.6"
],
"statement": "24000 is invalid_cursor_state in Class 24."
}claims · current-of
{
"claim_id": "current-of",
"limits": "A cursor can be validly declared yet still be unpositioned; declaration alone does not make CURRENT OF usable.",
"method": "Trace execCurrent and the documented DECLARE/CURRENT OF rules.",
"position": 1,
"runtime": [],
"sources": [
"src.execCurrent.18.6",
"doc.declare.18.6",
"doc.update.18.6",
"src.calls.REL_18_6"
],
"statement": "UPDATE or DELETE ... WHERE CURRENT OF requires a positioned, updatable cursor row; before FETCH the executor reports 24000."
}claims · runtime
{
"claim_id": "runtime",
"limits": "Only the before-FETCH CURRENT OF path was run; other cursor states use other source templates.",
"method": "Read the selected summaries and raw diagnostics.",
"position": 2,
"runtime": [],
"sources": [
"runtime.24000-batch1-latest2-20260909.latest",
"runtime.24000-batch1-pg10-20260909.pg10"
],
"statement": "The selected explicit transaction case returned 24000 before FETCH, entered INERROR, recovered after ROLLBACK, and then directly repaired the row on PG18.6 and PG10.21."
}messages · unpositioned
{
"limits": "The same template is also used by another CURRENT OF path; the cursor name is substituted at runtime.",
"message_id": "unpositioned",
"path": "",
"position": 0,
"raw": {
"id": "unpositioned",
"limits": "The same template is also used by another CURRENT OF path; the cursor name is substituted at runtime.",
"primary_template": "cursor \"%s\" is not positioned on a row",
"severity_source": "ERROR",
"sources": [
"src.execCurrent.18.6"
],
"sqlstate": "24000"
},
"severity": "ERROR",
"sources": [
"src.execCurrent.18.6"
],
"templates": [
{
"kind": "primary",
"literal": "cursor \" \" is not positioned on a row",
"position": 0,
"role": "",
"template": "cursor \"%s\" is not positioned on a row"
}
]
}runtimes · runtime.24000-batch1-latest2-20260909.latest · 18.6 (Homebrew) · passed
{
"cases": [
"current_of_before_fetch"
],
"limits": "",
"position": 0,
"raw": {
"case_manifest": {
"path": "verify/cases/24000/cases.json",
"sha256": "5a52a1c37d8702961515d9d32e7c5285ca3a9cebe0a16492858864ce933a1b1c"
},
"cases": [
"current_of_before_fetch"
],
"id": "runtime.24000-batch1-latest2-20260909.latest",
"observed": {
"after_error": "INERROR",
"after_rollback": "IDLE",
"primary": "cursor \"item_cursor\" is not positioned on a row",
"repaired_row": [
1,
"repaired"
],
"sqlstate": "24000"
},
"raw": "verify/results/24000-batch1-latest2-20260909/latest/raw.jsonl",
"raw_sha256": "567d6fb5a4c4f6e881050992aa833c79999d5018cba753b41021483f59e7069c",
"run_id": "24000-batch1-latest2-20260909",
"server_version": "18.6 (Homebrew)",
"server_version_num": 180006,
"snippet_registry": {
"path": "verify/cases/24000/snippets.json",
"sha256": "cafca57825378362df0527384614bf658bf86cd3101e1eb520ea0641819f2789"
},
"status": "passed",
"summary": "verify/results/24000-batch1-latest2-20260909/latest/summary.json",
"summary_sha256": "d14d912dad21157c8911b7ec6f16eb5b995b4efd387cca830b826a4ff17039d6",
"target": "latest"
},
"run_id": "24000-batch1-latest2-20260909",
"runtime_id": "runtime.24000-batch1-latest2-20260909.latest",
"server_version": "18.6 (Homebrew)",
"status": "passed",
"target": "latest"
}runtimes · runtime.24000-batch1-pg10-20260909.pg10 · 10.21 (Debian 10.21-1.pgdg90+1) · passed
{
"cases": [
"current_of_before_fetch"
],
"limits": "",
"position": 1,
"raw": {
"case_manifest": {
"path": "verify/cases/24000/cases.json",
"sha256": "5a52a1c37d8702961515d9d32e7c5285ca3a9cebe0a16492858864ce933a1b1c"
},
"cases": [
"current_of_before_fetch"
],
"id": "runtime.24000-batch1-pg10-20260909.pg10",
"observed": {
"after_error": "INERROR",
"after_rollback": "IDLE",
"primary": "cursor \"item_cursor\" is not positioned on a row",
"repaired_row": [
1,
"repaired"
],
"sqlstate": "24000"
},
"raw": "verify/results/24000-batch1-pg10-20260909/pg10/raw.jsonl",
"raw_sha256": "cf77c7f96913d2a33315afa186be2fd51eb100f69da7d5540ed708f41b5bde3f",
"run_id": "24000-batch1-pg10-20260909",
"server_version": "10.21 (Debian 10.21-1.pgdg90+1)",
"server_version_num": 100021,
"snippet_registry": {
"path": "verify/cases/24000/snippets.json",
"sha256": "cafca57825378362df0527384614bf658bf86cd3101e1eb520ea0641819f2789"
},
"status": "passed",
"summary": "verify/results/24000-batch1-pg10-20260909/pg10/summary.json",
"summary_sha256": "9a38d0b3ee7ffa8f3018ceb09045cc78ae4c2bccd004a6c369cf9cbc76089851",
"target": "pg10"
},
"run_id": "24000-batch1-pg10-20260909",
"runtime_id": "runtime.24000-batch1-pg10-20260909.pg10",
"server_version": "10.21 (Debian 10.21-1.pgdg90+1)",
"status": "passed",
"target": "pg10"
}sources · src.errcodes.18.6
{
"commit": "724edf9bde9d356724ad384a2e196edc3c9f80f7",
"docs_url": "",
"kind": "upstream_source",
"location": "",
"path": "src/backend/utils/errcodes.txt",
"position": 0,
"sha256": "6e8de346643ba84aa3c9c6a73360acfc7b2dfb89162c06c08ce9bf5bcd5bbcba",
"source_id": "src.errcodes.18.6",
"tag": "REL_18_6",
"url": "https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/utils/errcodes.txt"
}sources · src.calls.REL_18_6
{
"commit": "",
"docs_url": "",
"kind": "local_artifact",
"location": "",
"path": "raw/calls/REL_18_6.jsonl",
"position": 1,
"sha256": "9ee8a0e81d8f0825c5c1ae45583439859a26e602bdd4ce2f2a62aa278867ccbf",
"source_id": "src.calls.REL_18_6",
"tag": "",
"url": ""
}sources · src.execCurrent.18.6
{
"commit": "724edf9bde9d356724ad384a2e196edc3c9f80f7",
"docs_url": "",
"kind": "upstream_source",
"location": "",
"path": "src/backend/executor/execCurrent.c",
"position": 2,
"sha256": "45c8e48b3a5dc46f1014482dc88e0a26c25c3a4c403372112474d9171253d8a9",
"source_id": "src.execCurrent.18.6",
"tag": "REL_18_6",
"url": "https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/executor/execCurrent.c#L135-L138"
}sources · doc.declare.18.6
{
"commit": "724edf9bde9d356724ad384a2e196edc3c9f80f7",
"docs_url": "https://www.postgresql.org/docs/18/sql-declare.html",
"kind": "upstream_source",
"location": "",
"path": "doc/src/sgml/ref/declare.sgml",
"position": 3,
"sha256": "f1d72befb9a989aa32123560aea784a8bccfcb3d3930804a101d8245e5a3cc95",
"source_id": "doc.declare.18.6",
"tag": "REL_18_6",
"url": "https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/doc/src/sgml/ref/declare.sgml#L275-L285"
}sources · doc.update.18.6
{
"commit": "724edf9bde9d356724ad384a2e196edc3c9f80f7",
"docs_url": "https://www.postgresql.org/docs/18/sql-update.html",
"kind": "upstream_source",
"location": "",
"path": "doc/src/sgml/ref/update.sgml",
"position": 4,
"sha256": "47dd724cd724bd77478215a4853aa1f985996a0e5bba9b2451a1ad1ebb6e51fc",
"source_id": "doc.update.18.6",
"tag": "REL_18_6",
"url": "https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/doc/src/sgml/ref/update.sgml#L203-L211"
}Source references
Full definition and evidence JSON
Definition sources
center · PostgreSQL 18 · english-manuals:ea437c8357667a89d3001ec920203a65b1f5087d24d2e4eaa5d31bfe0929cc46
Text language: en · english-manuals:ea437c8357667a89d3001ec920203a65b1f5087d24d2e4eaa5d31bfe0929cc46