git clone https://github.com/Intense-Visions/harness-engineering
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/claude-code/api-conditional-requests" ~/.claude/skills/intense-visions-harness-engineering-api-conditional-requests-952641 && rm -rf "$T"
agents/skills/claude-code/api-conditional-requests/SKILL.mdConditional Requests
CONDITIONAL REQUESTS LET CLIENTS MAKE HTTP REQUESTS CONTINGENT ON RESOURCE STATE — PREVENTING REDUNDANT TRANSFERS WITH 304 NOT MODIFIED AND ENABLING OPTIMISTIC CONCURRENCY CONTROL WITH 412 PRECONDITION FAILED. WITHOUT CONDITIONAL REQUESTS, EVERY GET TRANSFERS THE FULL BODY AND EVERY PUT RISKS OVERWRITING CONCURRENT CHANGES.
When to Use
- Implementing cache revalidation for a polling client that frequently re-fetches the same resource
- Adding optimistic concurrency control to a PUT or PATCH endpoint to prevent lost updates
- Preventing duplicate creation with a conditional PUT (
)If-None-Match: * - Diagnosing 304 or 412 responses from an HTTP client or CDN
- Implementing efficient polling where clients receive a 304 when nothing has changed
- Deciding between ETag-based and timestamp-based conditional logic
- Building a file storage API (S3-style) where overwrite protection is required
- Reviewing code that re-fetches an entire resource list on every interval without conditional headers
Instructions
Key Concepts
-
If-None-Match — The client sends a stored ETag value; the server returns
if the current ETag matches (the resource has not changed), or304 Not Modified
with a new body and ETag if it has changed. Used on GET/HEAD requests for cache revalidation. Also supports200 OK
to check for resource non-existence (useful for conditional creation with PUT).If-None-Match: *GET /issues/42 If-None-Match: "etag-v7"If unchanged:
HTTP/1.1 304 Not Modified ETag: "etag-v7" Cache-Control: max-age=60If changed:
HTTP/1.1 200 OK ETag: "etag-v8" Content-Type: application/json { "id": 42, "title": "Updated title", ... } -
If-Modified-Since — The client sends a stored
timestamp; the server returnsLast-Modified
if the resource has not changed since that time. Weaker than ETags (second-level granularity). When both304 Not Modified
andIf-None-Match
are present,If-Modified-Since
takes precedence (RFC 9110).If-None-MatchGET /releases/latest If-Modified-Since: Thu, 09 Apr 2026 14:30:00 GMT -
If-Match — The client sends a stored ETag; the server proceeds only if the current ETag matches. If the ETag has changed (someone else modified the resource), the server returns
. Used on PUT/PATCH/DELETE to implement optimistic concurrency control — the write is rejected if the resource was modified since the client last read it.412 Precondition FailedPATCH /orders/ord_123 If-Match: "etag-v3" Content-Type: application/merge-patch+json { "status": "shipped" }If ETag still matches (no concurrent modification):
HTTP/1.1 200 OK ETag: "etag-v4"If ETag changed (concurrent write detected):
HTTP/1.1 412 Precondition Failed Content-Type: application/problem+json { "type": "https://api.example.com/errors/conflict", "title": "Precondition Failed", "detail": "The resource was modified by another client. Re-fetch and retry." } -
If-Unmodified-Since — Timestamp-based equivalent of
. Less precise than ETag-basedIf-Match
. The server proceeds only if the resource has not been modified since the specified time.If-Match -
If-None-Match: * — A special form that tests for non-existence. The server proceeds only if the resource does not exist. Used with PUT to implement safe creation: create the resource only if no resource exists at this URL. Returns
if a resource already exists.412 Precondition FailedPUT /configs/feature-flags If-None-Match: * Content-Type: application/json { "darkMode": false, "betaFeatures": [] } -
304 Not Modified — The server's response when a conditional GET/HEAD condition evaluates to "not changed." The response has no body; the client uses its cached copy. The response must still include relevant headers:
,ETag
,Last-Modified
,Cache-Control
,Vary
. OmittingExpires
breaks clients usingLast-Modified
on subsequent requests. This is the primary bandwidth-saving mechanism for polling APIs and CDN revalidation.If-Modified-Since
Worked Example
GitHub's REST API implements both 304-based cache revalidation and ETag-based write protection:
Initial fetch — store ETag and Last-Modified for future requests:
GET /repos/github/docs/contents/README.md Authorization: Bearer ghp_...
HTTP/1.1 200 OK ETag: "abc123" Last-Modified: Wed, 08 Apr 2026 10:00:00 GMT Cache-Control: private, max-age=60 Content-Type: application/vnd.github.v3+json { "name": "README.md", "sha": "abc123", "content": "...", "encoding": "base64" }
Polling revalidation — conditional GET with stored ETag:
GET /repos/github/docs/contents/README.md Authorization: Bearer ghp_... If-None-Match: "abc123"
HTTP/1.1 304 Not Modified ETag: "abc123" Cache-Control: private, max-age=60
No body — the client reuses the cached response. Bandwidth cost: ~200 bytes (headers only) vs. ~3 KB (full response).
Update file with optimistic concurrency (If-Match on PUT):
PUT /repos/github/docs/contents/README.md Authorization: Bearer ghp_... If-Match: "abc123" Content-Type: application/json { "message": "Update README", "content": "<base64-encoded-new-content>", "sha": "abc123" }
GitHub's file update API requires the
sha field (acting as the ETag equivalent in the request body) to match the current file SHA. If another commit changed the file, GitHub returns 409 Conflict (GitHub's variant of 412 Precondition Failed for this specific endpoint):
HTTP/1.1 409 Conflict Content-Type: application/json { "message": "Update is not a fast forward" }
Conditional creation — prevent overwrite of existing config:
PUT /orgs/myorg/actions/secrets/API_KEY Authorization: Bearer ghp_... If-None-Match: * Content-Type: application/json { "encrypted_value": "...", "key_id": "..." }
Proceeds only if the secret does not already exist. Returns
412 or 409 if the secret is already present.
Anti-Patterns
-
Ignoring ETags on write endpoints. A PUT endpoint that does not require
allows the "lost update" problem: two clients read the same resource, both modify it, and the second write silently overwrites the first. For any resource that might be concurrently modified, requireIf-Match
on mutating requests. ReturnIf-Match
if the client omits428 Precondition Required
on a protected resource.If-Match -
Using timestamps instead of ETags for write protection.
has 1-second granularity. Two writes in the same second will not be detected as conflicting. Generate ETags from content hashes for precise conflict detection. Use timestamps only as a fallback when ETag generation is not feasible.If-Unmodified-Since -
Not returning the new ETag after a successful conditional write. After a
orPATCH
that passesPUT
, the server creates a new resource version. Not returning the updated ETag in the response forces the client to re-fetch the resource to get the new ETag for subsequent writes. Always return the updatedIf-Match
header in the response to mutation requests.ETag -
Returning 304 without required headers. RFC 9110 requires that a
response include the same header fields that would have been sent in a304 Not Modified
response:200 OK
,Cache-Control
,ETag
,Vary
. AExpires
missing these headers causes caches to use stale directives from the original response, which may have already expired.304
Details
Optimistic vs. Pessimistic Concurrency
| Strategy | Mechanism | Tradeoff |
|---|---|---|
| Optimistic (HTTP) | ETag + If-Match; retry on 412 | No locks; low contention; requires client-side retry logic |
| Pessimistic (locks) | Database row-level lock; held until commit | Prevents conflicts; blocks other writers; lock timeout risk |
For REST APIs, optimistic concurrency via ETags is the standard approach. It scales horizontally without distributed lock coordination. The client reads the resource (gets an ETag), attempts a conditional write (with
If-Match), and retries with the new ETag on 412 Precondition Failed. See db-optimistic-locking for database-level implementation.
Conditional Requests and CDN Revalidation
CDNs use conditional requests internally when revalidating stale cache entries with the origin:
Client → CDN (stale cache) → Origin: GET /resource, If-None-Match: "old-etag" Origin → CDN: 304 Not Modified CDN → Client: 200 OK (from updated CDN cache)
This means ETag generation on the origin also benefits CDN efficiency — not just direct client interactions. Weak ETags (
W/"...") should be used when the response body may be transformed by the CDN (e.g., compressed differently) and the content is semantically equivalent across encodings.
Real-World Case Study: Atlassian Confluence REST API
Atlassian's Confluence REST API adopted
If-Match for page update endpoints after a data loss incident: two users editing the same wiki page simultaneously would overwrite each other's changes with no warning. After adding ETag-based optimistic locking, the API rejects stale writes with 409 Conflict and returns the current ETag, prompting the client to re-fetch and present a merge UI. In the first month after deployment, lost-update incidents dropped from approximately 200/week to zero. Polling clients using If-None-Match on frequently accessed pages reduced bandwidth consumption by 65%.
Source
- MDN — HTTP Conditional Requests
- RFC 9110 — HTTP Semantics, Section 13 (Conditional Requests)
- RFC 9110 — HTTP Semantics, Section 8.8 (Validator Fields)
- MDN — ETag
- MDN — If-Match
Process
- Identify the use case: cache revalidation (use
/If-None-Match
on GET) or write conflict detection (useIf-Modified-Since
on PUT/PATCH/DELETE).If-Match - Generate ETags on the server: hash the response body (MD5 or SHA-256) for strong ETags. Expose
andETag
in all GET responses for resources that may be conditionally requested.Last-Modified - For GET endpoints: check
against current ETag; returnIf-None-Match
with cache headers but no body if matching.304 Not Modified - For PUT/PATCH/DELETE endpoints: check
against current ETag; returnIf-Match
with a problem details body if not matching. Return the updated ETag in the success response.412 Precondition Failed - Run
to confirm skill files are well-formed.harness validate
Harness Integration
- Type: knowledge -- this skill is a reference document, not a procedural workflow.
- No tools or state -- consumed as context by other skills and agents.
- related_skills: api-http-caching, api-http-methods, db-optimistic-locking, api-status-codes
Success Criteria
- GET endpoints return
andETag
headers on all cacheable resource responses.Last-Modified - Conditional GET with a matching
returnsIf-None-Match
with no body and all required cache headers.304 Not Modified - PUT/PATCH/DELETE endpoints on concurrently-modifiable resources require
and returnIf-Match
on ETag mismatch.412 Precondition Failed - Successful mutation responses include the updated
header for the new resource version.ETag
responses include304 Not Modified
,Cache-Control
, andETag
headers per RFC 9110 requirements.Vary