HeyPuter/puter · error · HttpError
token_missing
token_missing
Error message
Missing Lock-Token header
What it means
Thrown by the WebDAV UNLOCK handler when the Lock-Token header is absent or does not contain a token matching the urn:uuid:<36-hex-chars> pattern that extractLockToken() recognizes. Returns 400 Bad Request per RFC 4918 §8.11.1.
Source
Thrown at src/backend/controllers/webdav/WebDAVController.ts:817
'Lock-Token': `<${token}>`,
...DAV_HEADERS,
})
.send(lockResponseXml(token, davPath, lockScope));
}
// -- UNLOCK ------------------------------------------------------
async #unlock(
req: Request,
res: Response,
davPath: string,
redis: unknown,
): Promise<void> {
const r = redis as import('ioredis').Cluster;
const tokenHeader = req.headers['lock-token'] as string | undefined;
const token = extractLockToken(tokenHeader);
if (!token)
throw new HttpError(400, 'Missing Lock-Token header', {
legacyCode: 'token_missing',
});
const lock = await getLockIfValid(r, token);
if (!lock) {
// Idempotent — if already expired, just 204.
res.status(204).end();
return;
}
if (lock.path !== davPath)
throw new HttpError(403, 'Lock token does not match this path', {
legacyCode: 'forbidden',
});
await deleteLock(r, token);
res.status(204).end();
}
View on GitHub (pinned to 908ec23eda)
Solutions
- Send the Lock-Token header exactly as returned by the prior LOCK response: Lock-Token: <urn:uuid:full-uuid>.
- Verify the header name is 'Lock-Token' (HTTP headers are case-insensitive but the name must be this).
- Confirm the UUID has all 36 characters including dashes (8-4-4-4-12).
- Re-issue a LOCK to obtain a fresh token if the original was lost.
Example fix
// before UNLOCK /file.txt HTTP/1.1 Lock-Token: some-made-up-string // after UNLOCK /file.txt HTTP/1.1 Lock-Token: (<urn:uuid:11111111-2222-3333-4444-555555555555>)
Defensive patterns
Strategy: validation
Validate before calling
function validLockTokenHeader(v) {
if (!v) return false;
return /<?urn:uuid:[0-9a-fA-F-]{36}>?/.test(v);
}
if (!validLockTokenHeader(headers['lock-token'])) {
throw new Error('Lock-Token header missing or malformed');
} Type guard
function isLockTokenHeader(v) {
return typeof v === 'string' && /<?urn:uuid:[0-9a-fA-F-]{36}>?/.test(v);
} Try / catch
try {
await webdavUnlock(path, token);
} catch (e) {
if (e.status === 400 && /Lock-Token/.test(e.message)) {
// re-LOCK to get a valid token, then UNLOCK
} else throw e;
} Prevention
- Always send the Lock-Token header exactly as returned by the LOCK response.
- Use the header name 'Lock-Token' (case-insensitive but the name must match).
- Keep the full UUID (8-4-4-4-12) intact when copying the token.
- Re-issue a LOCK if the original token string was lost or truncated.
When it happens
Trigger: An UNLOCK request with no Lock-Token header, a header set to an empty string, or a header whose value does not match the regex <?(urn:uuid:[0-9a-fA-F-]{36})>?. The handler reads the header from req.headers['lock-token'] (lowercase).
Common situations: Client forgot to send the Lock-Token header; sent the token in the wrong header name (e.g. 'If' instead of 'Lock-Token'); wrapped the token differently than <urn:uuid:...>; truncated or copy-pasted the UUID; case/format deviation in the UUID.
Related errors
- dest_is_not_a_directory
- permission_denied
- token_missing
- ${key} must be an integer between ${min} and ${max}
- App owner not found
AI-assisted analysis of HeyPuter/puter@908ec23eda (2026-08-12).
Data as JSON: /api/errors/55d22a74e667ea04.
Report an issue: GitHub.