{"record":{"id":"55d22a74e667ea04","repo":"HeyPuter/puter","slug":"token-missing-55d22a","errorCode":"token_missing","errorMessage":"Missing Lock-Token header","messagePattern":"Missing Lock-Token header","errorType":"http","errorClass":"HttpError","httpStatus":400,"severity":"error","filePath":"src/backend/controllers/webdav/WebDAVController.ts","lineNumber":817,"sourceCode":"                'Lock-Token': `<${token}>`,\n                ...DAV_HEADERS,\n            })\n            .send(lockResponseXml(token, davPath, lockScope));\n    }\n\n    // -- UNLOCK ------------------------------------------------------\n\n    async #unlock(\n        req: Request,\n        res: Response,\n        davPath: string,\n        redis: unknown,\n    ): Promise<void> {\n        const r = redis as import('ioredis').Cluster;\n        const tokenHeader = req.headers['lock-token'] as string | undefined;\n        const token = extractLockToken(tokenHeader);\n        if (!token)\n            throw new HttpError(400, 'Missing Lock-Token header', {\n                legacyCode: 'token_missing',\n            });\n\n        const lock = await getLockIfValid(r, token);\n        if (!lock) {\n            // Idempotent — if already expired, just 204.\n            res.status(204).end();\n            return;\n        }\n        if (lock.path !== davPath)\n            throw new HttpError(403, 'Lock token does not match this path', {\n                legacyCode: 'forbidden',\n            });\n\n        await deleteLock(r, token);\n        res.status(204).end();\n    }\n","sourceCodeStart":799,"sourceCodeEnd":835,"githubUrl":"https://github.com/HeyPuter/puter/blob/908ec23eda38526170322c3edf71ba45ecb1ca95/src/backend/controllers/webdav/WebDAVController.ts#L799-L835","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before\nUNLOCK /file.txt HTTP/1.1\nLock-Token: some-made-up-string\n\n// after\nUNLOCK /file.txt HTTP/1.1\nLock-Token: (<urn:uuid:11111111-2222-3333-4444-555555555555>)","handlingStrategy":"validation","validationCode":"function validLockTokenHeader(v) {\n  if (!v) return false;\n  return /<?urn:uuid:[0-9a-fA-F-]{36}>?/.test(v);\n}\nif (!validLockTokenHeader(headers['lock-token'])) {\n  throw new Error('Lock-Token header missing or malformed');\n}","typeGuard":"function isLockTokenHeader(v) {\n  return typeof v === 'string' && /<?urn:uuid:[0-9a-fA-F-]{36}>?/.test(v);\n}","tryCatchPattern":"try {\n  await webdavUnlock(path, token);\n} catch (e) {\n  if (e.status === 400 && /Lock-Token/.test(e.message)) {\n    // re-LOCK to get a valid token, then UNLOCK\n  } else throw e;\n}","preventionTips":["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."],"tags":["webdav","http","unlock","locking","validation"],"backgroundTag":null,"analyzedSha":"908ec23eda38526170322c3edf71ba45ecb1ca95","analyzedAt":"2026-08-12T20:53:15.911Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}