fix: repair a scheme-less WANDB_BASE_URL and stop losing W&B sink failures#57
Conversation
Confirmed live from inside a Modal sandboxThe open question on this PR was whether Modal egress to scaleai.wandb.io works, since the $0 repro only proved it from a laptop. It does. A swe-atlas-qna run started at 08:58:16 on a branch carrying this fix. One minute later: That entity listed zero projects before today. The project was created by the eval-sidecar running inside the Modal sandbox, on its And it is not just an empty project — it is streaming: Compare the failing run's So the entire Worth noting what this buys beyond the ticket: those Ready to merge from my side. |
f396e3f to
8d0ea6d
Compare
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>
ccc7d07 to
ec708e9
Compare
8d0ea6d to
6091efd
Compare
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| def normalize_wandb_base_url(environment: dict[str, str] | None = None) -> str | None: |
There was a problem hiding this comment.
why not just fix the url input?
Closes #52 (pending the live confirmation noted at the bottom).
The symptom
No harbor benchmark run has ever reported anything to the self-hosted W&B. Entity
shehab-yasserlisted zero projects. Every run's exportedsession/artifacts/wandb/state.jsonlooked like this:{"evaluation_ids": [], "next_step": 0, "run_id": "vero-a25e1cd8b7064d7e", "request_log_files": {}}…from the 2026-07-25 swe-atlas-qna run whose session contains 11 completed evaluations. So "nothing was logged" was never "nothing happened".
The mechanism
That exact fingerprint is what
SidecarWandbSink.__init__leaves behind whenwandb.init()throws._save_state()runs atruntime/wandb.py:225,_open_wandb_run()at:230.harbor/deployment.py:265-287catches anything out of the constructor and continues withwandb_sink = None, by design — observability must not take the eval path down.The cause, reproduced locally at $0
W&B parses
base_urlas a URL. The natural way to write a self-hosted host is rejected, and the rejection is indistinguishable from "W&B is unavailable".Same script with
https://prepended:The project auto-created server-side;
query_wandb_entity_projects(entity="shehab-yasser")went from[]to[vero-egress-probe]. Metrics logged,wandb.Artifactuploaded. Egress was never blocked, credentials were never missing, and the code was never unplumbed.Note the repo's own
swe-bench-pro/baseline/secrets.env.example:23already gets it right (WANDB_BASE_URL=https://scaleai.wandb.io); the live gitignoredsecrets.envhad drifted to the scheme-less form. Since the deployed secrets file is not in the repo, fixing only the file would leave the trap armed for the next operator.Also worth correcting
Issue #52 is titled "Weave traces". There is no Weave in this codebase —
weaveis not a dependency and is not importable in the built env.wandb.log_tracesdrivesSidecarWandbSink._log_trace, which uploads awandb.Artifactof typeevaluation_trace. Socount_weave_traceswould return nothing even on a perfectly healthy run; the right server-side check is whether the project exists and carries runs and artifacts.Changes
normalize_wandb_base_url()prependshttps://to a scheme-lessWANDB_BASE_URLand warns; called before bothwandb.init()sites. Values that already carry a scheme (includinghttp://localhost:8080) pass through untouched; unset stays unset.session/artifacts/wandb/init-error.jsonwith the exception type and message. The existinglogger.warningonly reaches the sidecar container's stderr, which no run artifact captures — grepping every artifact of the failing run for "wandb" returns nothing. That invisibility is what made a one-line config bug survive multiple runs.Tests:
test_scheme_less_wandb_base_url_is_repaired_before_init(repair, passthrough, unset, and that a sink repairs before opening its run) andtest_wandb_init_failure_is_recorded_in_the_session_artifacts(eval path survives, reason is durable).Still to confirm
The local probe proves the URL fix and that scaleai.wandb.io is reachable from this laptop. Modal-sandbox egress to it is not yet proven. A live benchmark run on this branch is queued; I will post the resulting project URL and artifact count here before this merges.
Greptile Summary
This PR fixes a silent W&B reporting failure caused by a scheme-less
WANDB_BASE_URL(e.g.scaleai.wandb.ioinstead ofhttps://scaleai.wandb.io) being rejected by W&B's URL validator, and adds durable error recording so a disabled sink is no longer invisible in exported run artifacts.normalize_wandb_base_url()which prependshttps://to a scheme-less env var and warns; called before bothwandb.init()call sites (_open_wandb_runandWandbEventSink.__init__).wandb/init-error.jsonto session artifacts when theSidecarWandbSinkconstructor fails, preserving the exception type and message in the archived run record.Confidence Score: 5/5
Safe to merge; the URL normalization and error recording are well-isolated, and the eval path is unaffected on any failure.
The changes are tightly scoped: a pure utility function that mutates one env var idempotently, and an error-recording block that is fully wrapped in a try/except. Neither touches the evaluation engine or any data path. The only minor concern is a narrow except OSError guard that could theoretically let a non-OS exception escape, but the write operation involves only plain strings so this is not a realistic failure mode today.
Files Needing Attention: The except OSError guard in deployment.py is the one place that could be marginally tightened; all other files are straightforward.
Important Files Changed
Sequence Diagram
sequenceDiagram participant D as deployment.py build_harbor_components participant S as SidecarWandbSink.__init__ participant O as _open_wandb_run participant N as normalize_wandb_base_url participant W as wandb.init() participant A as ArtifactStore D->>S: SidecarWandbSink(project, session_id, ...) S->>A: _save_state() → wandb/state.json S->>O: _open_wandb_run(wandb_dir, ...) O->>N: normalize_wandb_base_url() alt WANDB_BASE_URL scheme-less N->>N: prepend https://, warn N-->>O: https://scaleai.wandb.io else already has scheme or unset N-->>O: passthrough / None end O->>W: "client.init(**init_kwargs)" alt wandb.init() succeeds W-->>S: run object S-->>D: SidecarWandbSink instance else wandb.init() raises W-->>O: Exception O-->>S: Exception S-->>D: Exception D->>A: write_json(wandb/init-error.json) Note over D: wandb_sink = None, eval path continues endReviews (3): Last reviewed commit: "fix: repair a scheme-less WANDB_BASE_URL..." | Re-trigger Greptile