Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 58 additions & 21 deletions verifiers/v1/tasksets/harbor/taskset.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,13 @@
import io
import tarfile
import tempfile
import threading
import tomllib
from functools import lru_cache
from pathlib import Path

from pydantic import Field

try:
from harbor.cli.download import download_command
except ImportError as e:
raise ImportError(
"Harbor tasksets require Harbor. Install it with: "
"`uv sync --python 3.12 --extra harbor`"
) from e

from verifiers.v1.decorators import reward
from verifiers.v1.errors import SandboxError
from verifiers.v1.runtimes import Runtime
Expand All @@ -45,6 +38,7 @@
from verifiers.v1.types import StrictBaseModel

CACHE = Path.home() / ".cache" / "harbor"
HARBOR_INSTALL_HINT = "uv sync --python 3.12 --extra harbor"


class HarborConfig(TasksetConfig):
Expand Down Expand Up @@ -117,6 +111,61 @@ def cache_dir(config: HarborConfig) -> Path:
return CACHE / name


def download_with_harbor(config: HarborConfig, output_dir: Path) -> None:
try:
from harbor.cli import download as harbor_download
except ImportError as exc:
raise ImportError(
f"Harbor tasksets require Harbor. Install it with: `{HARBOR_INSTALL_HINT}`"
) from exc

registry_path = config.registry_path
if registry_path is not None and config.repo is None:
registry_path = registry_path.expanduser()

error: Exception | None = None
output = ""

def run_download() -> None:
nonlocal error, output
capture = harbor_download.console.capture()
try:
with capture:
harbor_download.download_command(
name=config.dataset,
output_dir=output_dir,
overwrite=False,
registry_url=config.registry_url,
registry_path=registry_path,
repo=config.repo,
export=True,
cache=False,
)
except Exception as exc:
error = exc
finally:
output = capture.get().strip()

thread = threading.Thread(target=run_download, name="harbor-download")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium harbor/taskset.py:149

download_with_harbor() starts a non-daemon Thread and blocks on thread.join(). If the caller is interrupted (e.g. Ctrl-C) during the join, the main thread unwinds and TemporaryDirectory.__exit__() deletes output_dir, but the background download thread keeps running because it is neither stopped nor a daemon — the process can hang on shutdown waiting for it to finish, and the worker races against deletion of the directory it's writing to. Marking the thread as daemon=True lets the process exit without waiting for it.

Suggested change
thread = threading.Thread(target=run_download, name="harbor-download")
thread = threading.Thread(target=run_download, name="harbor-download", daemon=True)
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @verifiers/v1/tasksets/harbor/taskset.py around line 149:

`download_with_harbor()` starts a non-daemon `Thread` and blocks on `thread.join()`. If the caller is interrupted (e.g. `Ctrl-C`) during the join, the main thread unwinds and `TemporaryDirectory.__exit__()` deletes `output_dir`, but the background download thread keeps running because it is neither stopped nor a daemon — the process can hang on shutdown waiting for it to finish, and the worker races against deletion of the directory it's writing to. Marking the thread as `daemon=True` lets the process exit without waiting for it.

thread.start()
thread.join()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worker thread failures not surfaced

Medium Severity

download_with_harbor starts Harbor in a worker thread and only records failures caught as Exception from download_command. Uncaught errors in that thread (including setup before the inner try, or BaseException subclasses) are not stored in error, so after thread.join() the caller can return normally even though the download never completed successfully.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 636a96f. Configure here.


if error is None:
return

exit_code = getattr(error, "exit_code", None)
message = f"Harbor download failed for {config.dataset!r}"
if exit_code is not None:
message = f"{message} with exit code {exit_code}"
details = [output] if output else []
error_text = str(error).strip()
if error_text and exit_code is None:
details.append(error_text)
if details:
message = f"{message}:\n" + "\n".join(details)
raise RuntimeError(message) from error


def dataset_dir(config: HarborConfig) -> Path:
"""Download a Harbor task or dataset package, cached on first use.

Expand All @@ -131,19 +180,7 @@ def dataset_dir(config: HarborConfig) -> Path:
# Publish only a complete Harbor export to the cache.
with tempfile.TemporaryDirectory(dir=CACHE) as temp:
export_dir = Path(temp) / "export"
registry_path = config.registry_path
if registry_path is not None and config.repo is None:
registry_path = registry_path.expanduser()
download_command(
name=config.dataset,
output_dir=export_dir,
overwrite=False,
registry_url=config.registry_url,
registry_path=registry_path,
repo=config.repo,
export=True,
cache=False,
)
download_with_harbor(config, export_dir)
try:
export_dir.rename(out)
except OSError:
Expand Down
Loading