Spaces:
Running
Running
Explain OAuth bucket write failures safely
Browse filesSource commit: c290d348b11f29b480df28112b4c7558cc0fad1f
- Dockerfile +2 -1
- research/research_workspace.py +37 -6
Dockerfile
CHANGED
|
@@ -9,7 +9,8 @@ RUN uv pip install --system --no-cache \
|
|
| 9 |
fast-agent-mcp==0.9.27 \
|
| 10 |
'fastmcp[apps]==3.4.4' \
|
| 11 |
prefab-ui==0.20.2 \
|
| 12 |
-
huggingface_hub \
|
|
|
|
| 13 |
pillow
|
| 14 |
|
| 15 |
COPY --link ./ /app
|
|
|
|
| 9 |
fast-agent-mcp==0.9.27 \
|
| 10 |
'fastmcp[apps]==3.4.4' \
|
| 11 |
prefab-ui==0.20.2 \
|
| 12 |
+
huggingface_hub==1.24.0 \
|
| 13 |
+
hf-xet==1.5.2 \
|
| 14 |
pillow
|
| 15 |
|
| 16 |
COPY --link ./ /app
|
research/research_workspace.py
CHANGED
|
@@ -6,20 +6,40 @@ import hashlib
|
|
| 6 |
import json
|
| 7 |
import os
|
| 8 |
import re
|
|
|
|
| 9 |
from contextvars import ContextVar
|
| 10 |
from dataclasses import dataclass
|
| 11 |
from datetime import UTC, datetime
|
| 12 |
-
from typing import Any
|
| 13 |
from uuid import uuid4
|
| 14 |
|
|
|
|
|
|
|
| 15 |
from huggingface_hub import HfApi, get_token
|
| 16 |
from huggingface_hub.errors import BucketNotFoundError
|
| 17 |
|
| 18 |
-
|
| 19 |
-
from fast_agent.mcp.server.common import normalize_serve_oauth_provider
|
| 20 |
|
| 21 |
|
| 22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
|
| 25 |
@dataclass(frozen=True, slots=True)
|
|
@@ -104,8 +124,11 @@ def ensure_workspace(
|
|
| 104 |
token=token,
|
| 105 |
)
|
| 106 |
except Exception as exc:
|
| 107 |
-
|
| 108 |
-
|
|
|
|
|
|
|
|
|
|
| 109 |
) from exc
|
| 110 |
marker_paths = (
|
| 111 |
f"{root}scratch/.workspace.json",
|
|
@@ -125,6 +148,14 @@ def ensure_workspace(
|
|
| 125 |
)
|
| 126 |
|
| 127 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
def _token(auth: AgentAuth | None) -> str | None:
|
| 129 |
if auth is not None and auth.token:
|
| 130 |
return auth.token
|
|
|
|
| 6 |
import json
|
| 7 |
import os
|
| 8 |
import re
|
| 9 |
+
from collections.abc import Mapping
|
| 10 |
from contextvars import ContextVar
|
| 11 |
from dataclasses import dataclass
|
| 12 |
from datetime import UTC, datetime
|
| 13 |
+
from typing import Any
|
| 14 |
from uuid import uuid4
|
| 15 |
|
| 16 |
+
from fast_agent import AgentAuth
|
| 17 |
+
from fast_agent.mcp.server.common import normalize_serve_oauth_provider
|
| 18 |
from huggingface_hub import HfApi, get_token
|
| 19 |
from huggingface_hub.errors import BucketNotFoundError
|
| 20 |
|
| 21 |
+
_SAFE_SEGMENT = re.compile(r"[^A-Za-z0-9._-]+")
|
|
|
|
| 22 |
|
| 23 |
|
| 24 |
+
class WorkspaceProvisionError(RuntimeError):
|
| 25 |
+
"""Safe, user-facing workspace setup failure."""
|
| 26 |
+
|
| 27 |
+
code = "HF_WORKSPACE_PROVISION_FAILED"
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
class WorkspaceWriteAuthorizationError(WorkspaceProvisionError):
|
| 31 |
+
"""The caller can read the workspace bucket but cannot write to it."""
|
| 32 |
+
|
| 33 |
+
code = "HF_BUCKET_WRITE_NOT_AUTHORIZED"
|
| 34 |
+
|
| 35 |
+
def __init__(self) -> None:
|
| 36 |
+
super().__init__(
|
| 37 |
+
"Your Hugging Face connection can read the existing research workspace "
|
| 38 |
+
"but is not authorized to write to it. The `contribute-repos` permission "
|
| 39 |
+
"alone covers only Buckets created by the same OAuth app. Reconnect after "
|
| 40 |
+
"this app's requested write access is available, or use a new workspace "
|
| 41 |
+
"created by this app."
|
| 42 |
+
)
|
| 43 |
|
| 44 |
|
| 45 |
@dataclass(frozen=True, slots=True)
|
|
|
|
| 124 |
token=token,
|
| 125 |
)
|
| 126 |
except Exception as exc:
|
| 127 |
+
if _is_xet_write_forbidden(exc):
|
| 128 |
+
raise WorkspaceWriteAuthorizationError from exc
|
| 129 |
+
raise WorkspaceProvisionError(
|
| 130 |
+
"The research workspace could not be initialized. Reconnect your "
|
| 131 |
+
"Hugging Face account and try again."
|
| 132 |
) from exc
|
| 133 |
marker_paths = (
|
| 134 |
f"{root}scratch/.workspace.json",
|
|
|
|
| 148 |
)
|
| 149 |
|
| 150 |
|
| 151 |
+
def _is_xet_write_forbidden(exc: BaseException) -> bool:
|
| 152 |
+
"""Classify hf-xet 1.5.x's opaque write-token authorization failure."""
|
| 153 |
+
if not isinstance(exc, ConnectionError):
|
| 154 |
+
return False
|
| 155 |
+
detail = str(exc)
|
| 156 |
+
return "403 Forbidden" in detail and "/xet-write-token" in detail
|
| 157 |
+
|
| 158 |
+
|
| 159 |
def _token(auth: AgentAuth | None) -> str | None:
|
| 160 |
if auth is not None and auth.token:
|
| 161 |
return auth.token
|