{"record":{"id":"1431c9d80fb57df0","repo":"unslothai/unsloth","slug":"native-path-grant-has-expired","errorCode":null,"errorMessage":"Native path grant has expired.","messagePattern":"Native path grant has expired\\.","errorType":"validation","errorClass":"NativePathLeaseError","httpStatus":400,"severity":"error","filePath":"studio/backend/utils/native_path_leases.py","lineNumber":332,"sourceCode":"        \"expires_at_ms\",\n        \"nonce\",\n    )\n    missing = [key for key in required if key not in payload]\n    if missing:\n        raise NativePathLeaseError(\"Native path grant payload is missing required fields.\")\n    if _required_int(payload, \"version\") != 1:\n        raise NativePathLeaseError(\"Native path grant version is unsupported.\")\n    if payload[\"operation\"] != operation:\n        raise NativePathLeaseError(\"Native path grant operation is invalid.\")\n    if expected_kind and payload[\"path_kind\"] != expected_kind:\n        raise NativePathLeaseError(\"Native path grant kind is invalid.\")\n    now_ms = int(time.time() * 1000)\n    issued_at_ms = _required_int(payload, \"issued_at_ms\")\n    expires_at_ms = _required_int(payload, \"expires_at_ms\")\n    if issued_at_ms >= expires_at_ms:\n        raise NativePathLeaseError(\"Native path grant timestamps are inconsistent.\")\n    if expires_at_ms <= now_ms:\n        raise NativePathLeaseError(\"Native path grant has expired.\")\n    if issued_at_ms > now_ms + 30_000:\n        raise NativePathLeaseError(\"Native path grant issue time is invalid.\")\n    for key in (\"canonical_path\", \"nonce\", \"token_id_hash\", \"display_label\"):\n        raw = payload.get(key)\n        if raw is None:\n            continue\n        if \"\\x00\" in str(raw):\n            raise NativePathLeaseError(\"Native path grant contains invalid characters.\")\n\n\ndef _validate_current_stat(\n    grant: NativePathGrant, identity_options: tuple[tuple[int, int], ...]\n) -> tuple[int, int] | None:\n    try:\n        st = os.lstat(grant.canonical_path)\n    except OSError as exc:\n        raise NativePathLeaseError(\"Native path is no longer accessible.\") from exc\n    if _stat_module.S_ISLNK(st.st_mode):","sourceCodeStart":314,"sourceCodeEnd":350,"githubUrl":"https://github.com/unslothai/unsloth/blob/203007d19051dcd2ae33876786d117c99f6b0368/studio/backend/utils/native_path_leases.py#L314-L350","documentation":"The signed native path grant's expires_at_ms is in the past relative to the backend's current clock, so the lease is no longer honored. Grants are short-lived capabilities bound to a file the user just picked in the desktop UI; the backend refuses to read a native path once the lease window has closed. This is a freshness guarantee, not a permanent entitlement. The check uses the backend process clock, so clock skew between the Tauri shell and the backend also matters.","triggerScenarios":"verify_native_path_lease() is called with a lease whose expires_at_ms <= int(time.time()*1000). Typical when the user picked a file, then submitted the job minutes later; when the request was retried/queued past the TTL; or when the backend clock is ahead of the machine clock that signed the grant.","commonSituations":"Long dwell time on a form after a file-picker grant was issued; paginated/retried uploads replaying an old grant; a laptop resumed from sleep (clock jumped); NTP drift between processes or containers; debugging sessions where the grant was captured hours earlier.","solutions":["Request a fresh grant: re-trigger the native file selection (or the frontend's grant-refresh endpoint) and resend the request immediately.","Shorten the gap between selection and submission so the operation lands inside the grant TTL.","If it reproduces consistently, compare the signer's clock and the backend's clock (date +%s%3N on both) and fix NTP/timezone or container clock skew.","If a longer window is genuinely needed, raise the TTL in the Rust signer (and keep issued_at < expires)."],"exampleFix":"// before (frontend)\nconst lease = await pickNativeFile(path); // grant cached for the whole session\nsubmitJob(lease); // fails hours later\n\n// after\nasync function submitJob(path) {\n  const lease = await refreshNativeGrant(path); // fresh, short-lived grant\n  await api.post('/job', { lease });\n}","handlingStrategy":"retry","validationCode":"import base64, json, time\n\ndef grant_still_valid(lease: str, skew_ms: int = 0) -> bool:\n    payload = json.loads(base64.urlsafe_b64decode(lease.split('.')[0] + '=='))\n    return int(payload['expires_at_ms']) > int(time.time() * 1000) + skew_ms","typeGuard":null,"tryCatchPattern":"try:\n    grant = verify_native_path_lease(lease, operation='read')\nexcept NativePathLeaseError as exc:\n    if 'has expired' in str(exc):\n        lease = request_fresh_grant()   # re-pick / refresh endpoint\n        grant = verify_native_path_lease(lease, operation='read')\n    else:\n        raise","preventionTips":["Submit the job immediately after file selection; do not cache leases across screens.","Wrap long-running user flows with a grant-refresh step before the final API call.","Verify signer and backend clocks agree (same host) to avoid false expiries."],"tags":["native-path-lease","expiry","clock-skew","desktop"],"backgroundTag":null,"analyzedSha":"203007d19051dcd2ae33876786d117c99f6b0368","analyzedAt":"2026-08-15T02:48:39.846Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}