Skip to content

Commit f396e3f

Browse files
fix: repair a scheme-less WANDB_BASE_URL and stop losing sink failures
Every harbor benchmark run has reported nothing to the self-hosted W&B: `shehab-yasser` listed zero projects, and each run's exported `artifacts/wandb/state.json` showed a run_id minted with `evaluation_ids: []`, `next_step: 0`, `request_log_files: {}` — even for a session that completed 11 evaluations. That fingerprint is exactly what `SidecarWandbSink.__init__` leaves when `wandb.init()` raises: state is written (wandb.py:225) before the run is opened (wandb.py:230), and `deployment.py` catches anything from the constructor and continues without W&B. Reproduced locally against scaleai.wandb.io, $0, deterministic: WANDB_BASE_URL=scaleai.wandb.io pydantic_core.ValidationError: 1 validation error for Settings base_url Input should be a valid URL, relative URL without a base [type=url_parsing, input_value='scaleai.wandb.io'] W&B parses `base_url` as a URL, so the natural way to write a self-hosted host silently costs the run all of its reporting. With `https://` prepended, the same script logs metrics, uploads an artifact and auto-creates the project. Egress was never the problem. Two changes: - `normalize_wandb_base_url()` prepends `https://` to a scheme-less `WANDB_BASE_URL` (and warns), called before both `wandb.init()` sites. Already-qualified values, including plain `http://localhost`, pass through. - The swallowed init failure now also lands in `session/artifacts/wandb/init-error.json`. The existing `logger.warning` goes to the sidecar container's stderr, which no run artifact captures, so a disabled sink was indistinguishable from a healthy run that logged nothing. Observability still never takes the eval path down. Refs #52. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent ccc7d07 commit f396e3f

4 files changed

Lines changed: 147 additions & 1 deletion

File tree

vero/src/vero/harbor/deployment.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
VerificationSelection,
3333
VerificationTarget,
3434
)
35+
from vero.runtime.artifacts import ArtifactStore
3536
from vero.sandbox import LocalSandbox
3637
from vero.workspace import GitWorkspace
3738

@@ -278,13 +279,28 @@ async def build_harbor_components(config: dict) -> SidecarComponents:
278279
log_traces=parsed.wandb.log_traces,
279280
)
280281
engine.listeners.append(wandb_sink)
281-
except Exception:
282+
except Exception as error:
282283
logger.warning(
283284
"W&B reporting disabled: the sidecar sink could not be "
284285
"initialized; the evaluation sidecar continues without it",
285286
exc_info=True,
286287
)
287288
wandb_sink = None
289+
# The warning above goes to the sidecar container's stderr, which no
290+
# run artifact captures — a silently disabled sink then looks exactly
291+
# like a healthy run that logged nothing. Record why in the session
292+
# artifacts, which are archived into the exported run record.
293+
try:
294+
ArtifactStore(session_dir / "artifacts").write_json(
295+
"wandb/init-error.json",
296+
{
297+
"project": parsed.wandb.project,
298+
"error_type": type(error).__name__,
299+
"error": str(error),
300+
},
301+
)
302+
except OSError:
303+
logger.warning("could not record the W&B init failure", exc_info=True)
288304

289305
usage_path = (
290306
Path(parsed.inference_usage_path)

vero/src/vero/runtime/wandb.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import hashlib
77
import json
88
import logging
9+
import os
910
import tempfile
1011
from pathlib import Path
1112
from typing import Any
@@ -19,6 +20,31 @@
1920
logger = logging.getLogger(__name__)
2021

2122

23+
def normalize_wandb_base_url(environment: dict[str, str] | None = None) -> str | None:
24+
"""Give a scheme-less ``WANDB_BASE_URL`` the ``https://`` it needs.
25+
26+
A self-hosted host is naturally written ``scaleai.wandb.io``, but W&B's
27+
settings model parses ``base_url`` as a URL and rejects that with
28+
``Input should be a valid URL, relative URL without a base``. The error
29+
surfaces out of ``wandb.init()``, which callers treat as "W&B unavailable",
30+
so one missing scheme silently costs a run all of its reporting.
31+
32+
Returns the value in effect, or None when the variable is unset.
33+
"""
34+
environ = os.environ if environment is None else environment
35+
value = (environ.get("WANDB_BASE_URL") or "").strip()
36+
if not value or "://" in value:
37+
return value or None
38+
corrected = f"https://{value}"
39+
environ["WANDB_BASE_URL"] = corrected
40+
logger.warning(
41+
"WANDB_BASE_URL %r has no scheme and would be rejected by W&B; using %r",
42+
value,
43+
corrected,
44+
)
45+
return corrected
46+
47+
2248
def _open_wandb_run(
2349
*,
2450
project: str,
@@ -43,6 +69,7 @@ def _open_wandb_run(
4369
raise RuntimeError(
4470
"W&B reporting requires `pip install scale-vero[wandb]`"
4571
) from error
72+
normalize_wandb_base_url()
4673
wandb_dir.mkdir(parents=True, exist_ok=True)
4774
stable_id = run_id or ("vero-" + hashlib.sha256(session_id.encode()).hexdigest()[:16])
4875
init_kwargs: dict[str, Any] = {
@@ -96,6 +123,7 @@ def __init__(
96123
"W&B reporting requires `pip install scale-vero[wandb]`"
97124
) from error
98125

126+
normalize_wandb_base_url()
99127
wandb_dir = session_dir / "artifacts" / "wandb"
100128
wandb_dir.mkdir(parents=True, exist_ok=True)
101129
self.artifacts = ArtifactStore(session_dir / "artifacts")

vero/tests/test_v05_harbor_deployment.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,3 +268,76 @@ class Result:
268268
assert (
269269
exported / "requests/requests-00001.jsonl"
270270
).read_text() == '{"scope":"producer"}\n'
271+
272+
273+
@pytest.mark.asyncio
274+
async def test_wandb_init_failure_is_recorded_in_the_session_artifacts(tmp_path):
275+
# A sink that cannot start only logs to the sidecar container's stderr, which
276+
# no run artifact captures — so a disabled sink is indistinguishable from a
277+
# healthy run that logged nothing. Leave the reason behind in the session.
278+
trusted = tmp_path / "trusted"
279+
agent = tmp_path / "agent"
280+
_repo(trusted, "VALUE = 1\n")
281+
_repo(agent, "VALUE = 1\n")
282+
cases = tmp_path / "cases.json"
283+
cases.write_text('[{"id":"task","task_name":"org/task"}]')
284+
evaluation_set = EvaluationSet(name="benchmark")
285+
objective = ObjectiveSpec(
286+
selector=MetricSelector(metric="score"),
287+
direction="maximize",
288+
)
289+
session_dir = tmp_path / "state/session"
290+
config = {
291+
"repo_path": str(trusted),
292+
"agent_repo_path": str(agent),
293+
"session_dir": str(session_dir),
294+
"backends": {
295+
"backend": {
296+
"task_source": "org/benchmark@1.0",
297+
"agent_import_path": "program:Agent",
298+
"cases_path": str(cases),
299+
"harbor_requirement": "harbor==0.1.17",
300+
"uv_executable": sys.executable,
301+
}
302+
},
303+
"access_policies": [],
304+
"budgets": [],
305+
"selection": {
306+
"mode": "auto_best",
307+
"backend_id": "backend",
308+
"evaluation_set": evaluation_set.model_dump(mode="json"),
309+
"objective": objective.model_dump(mode="json"),
310+
"baseline_version": "HEAD",
311+
},
312+
"targets": [
313+
{
314+
"reward_key": "reward",
315+
"backend_id": "backend",
316+
"evaluation_set": evaluation_set.model_dump(mode="json"),
317+
"objective": objective.model_dump(mode="json"),
318+
}
319+
],
320+
"admin_volume": str(tmp_path / "state/admin"),
321+
"wandb": {"project": "vero-tests"},
322+
}
323+
324+
import vero.runtime.wandb as wandb_module
325+
326+
def _explode(**kwargs):
327+
raise ValueError("Input should be a valid URL, relative URL without a base")
328+
329+
original = wandb_module.SidecarWandbSink
330+
wandb_module.SidecarWandbSink = _explode
331+
try:
332+
components = await build_harbor_components(config)
333+
finally:
334+
wandb_module.SidecarWandbSink = original
335+
336+
# The eval path survives: observability never takes the sidecar down.
337+
assert components.telemetry is None
338+
recorded = json.loads(
339+
(session_dir / "artifacts/wandb/init-error.json").read_text()
340+
)
341+
assert recorded["project"] == "vero-tests"
342+
assert recorded["error_type"] == "ValueError"
343+
assert "valid URL" in recorded["error"]

vero/tests/test_v05_wandb.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import json
4+
import os
45
from datetime import UTC, datetime, timedelta
56
from pathlib import Path
67

@@ -402,3 +403,31 @@ def test_sidecar_wandb_telemetry_is_best_effort(tmp_path: Path):
402403
poller.poll_once() # must not raise
403404
assert client.run.logged == []
404405
assert client.run.artifacts == []
406+
407+
408+
def test_scheme_less_wandb_base_url_is_repaired_before_init(tmp_path: Path, monkeypatch):
409+
"""A self-hosted host written without a scheme must not silently kill reporting.
410+
411+
W&B parses `base_url` as a URL, so `WANDB_BASE_URL=scaleai.wandb.io` raises
412+
out of `wandb.init()` and the sidecar disables W&B for the whole run.
413+
"""
414+
from vero.runtime.wandb import normalize_wandb_base_url
415+
416+
monkeypatch.setenv("WANDB_BASE_URL", "scaleai.wandb.io")
417+
assert normalize_wandb_base_url() == "https://scaleai.wandb.io"
418+
assert os.environ["WANDB_BASE_URL"] == "https://scaleai.wandb.io"
419+
420+
# Already-qualified values, including plain http, are left exactly as given.
421+
monkeypatch.setenv("WANDB_BASE_URL", "http://localhost:8080")
422+
assert normalize_wandb_base_url() == "http://localhost:8080"
423+
assert os.environ["WANDB_BASE_URL"] == "http://localhost:8080"
424+
425+
monkeypatch.delenv("WANDB_BASE_URL", raising=False)
426+
assert normalize_wandb_base_url() is None
427+
428+
# And the repair happens before a sink opens its run.
429+
monkeypatch.setenv("WANDB_BASE_URL", "scaleai.wandb.io")
430+
SidecarWandbSink(
431+
project="v", session_id="s", session_dir=tmp_path / "session", client=FakeWandb()
432+
)
433+
assert os.environ["WANDB_BASE_URL"] == "https://scaleai.wandb.io"

0 commit comments

Comments
 (0)