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

  1. Send the Lock-Token header exactly as returned by the prior LOCK response: Lock-Token: <urn:uuid:full-uuid>.
  2. Verify the header name is 'Lock-Token' (HTTP headers are case-insensitive but the name must be this).
  3. Confirm the UUID has all 36 characters including dashes (8-4-4-4-12).
  4. 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

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


AI-assisted analysis of HeyPuter/puter@908ec23eda (2026-08-12). Data as JSON: /api/errors/55d22a74e667ea04. Report an issue: GitHub.