Repository navigation
feat(items): idempotency keys for item create (TASK-3585) - #2065
Merged
Merged
Conversation
A create carries op_key (body) or Idempotency-Key (header). The key is claimed before the write and its answer (the created item's id) is stored inside the create's own transaction, so a retry whose first answer was lost returns that item instead of creating a twin. A refused create releases its key; a key reused for a different create is 422 idempotency_key_reused; one still in flight is 409 idempotency_in_flight. A replay whose item has since been deleted answers 410 idempotency_target_gone. CLI: `pad item create --op-key`, a fresh key per invocation by default, and one same-key retry of a 5xx or transport error, only when the server advertises idempotency_keys. MCP: pad_item create accepts op_key on both transports (remote maps it into the body; stdio passes --op-key). ToolSurfaceVersion 0.90 -> 0.91 (additive). Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS
xarmian
added a commit
that referenced
this pull request
Oct 12, 2026
… (TASK-3585) (#2088) Retro CONVE-735 finding on #2065. handleCreateItem enforced the workspace item limit before claiming the idempotency key, so a create that succeeded AT the limit and lost its answer could not replay: its retry met the limit its own success had reached. The claim now comes first; the request is not modified in between, so the key's hash is unchanged, and every refusal after it (the limit included) still releases the key. Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TASK-3585, create: idempotency keys for item create. This builds on #2062 (patch keys plus the capability gate).
What changes
POST /workspaces/{ws}/collections/{c}/itemsacceptsop_keyin the body or anIdempotency-Keyheader. The key is claimed before the write, and the answer (the created item's id) is stored inside the create's own transaction (ItemCreate.AfterWriteTx, run bycreateItemTxWithID). A retry whose first answer was lost therefore gets the item back, as it stands now, withIdempotent-Replayed: true, and no twin is created.idempotency_key_reused.idempotency_in_flight.idempotency_target_gone.internal/server/idempotency_keys.go, for append, copy and push next.pad item create --op-key, with a fresh key per invocation by default. On a 5xx or transport error it retries once with the same key, and only when the server advertisesidempotency_keys. An older server ignores the key, so a retry there could create a twin.pad_itemcreate acceptsop_keyon both transports. Remote maps it into the body; stdio passes--op-keythrough the generic flag mapping.in_flightandkey_reusedare already structured for any route: remote viaerrors.go, stdio viamain.go's top-level marker.ToolSurfaceVersiongoes from 0.90 to 0.91 (additive). The golden files, README and instructions.md are updated.Known gap
idempotency_target_gone(410) is not on the structured allow-list, so it reaches MCP as a generic error. The version.go changelog says so.Tests
cmd/pad/task3585_create_retry_test.go: same-key retry, no retry without the capability, no retry on a 4xx, a key that is either the one given or a fresh one each time. The no-capability test fails with the gate removed.internal/server/task3585_create_key_test.go: replay returns the same item with no twin, a refusal releases the key, the bodyop_keyreplays.internal/mcp/task3585_op_key_test.go::TestTASK3585_MCPCreateOpKey: fails ifop_keyis dropped frommapItemCreate.internal/serverandinternal/materializepackages green (after rebuilding this worktree's bundles), pluscmd/pad,internal/cli,internal/mcp,internal/modelsand the repo guard tests on the rebased head. Lint is clean.https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS