Skip to content

refactor(n2): dedupe data-URL header validation in n2_payload.py - #376

Merged
dhruvbatra merged 1 commit into
mainfrom
claude/cool-brahmagupta-paer04
Sep 4, 2026
Merged

refactor(n2): dedupe data-URL header validation in n2_payload.py#376
dhruvbatra merged 1 commit into
mainfrom
claude/cool-brahmagupta-paer04

Conversation

@dhruvbatra

@dhruvbatra dhruvbatra commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Problem

yutori/navigator/n2_payload.py has two private helpers that each hand-roll the exact same base64 data-URL validation:

def _decode_data_url(url: str) -> "tuple[bytes, str]":
    if not isinstance(url, str) or not url.startswith("data:") or "," not in url:
        raise ValueError("n2 screenshots must be base64 data URLs")
    header, encoded = url.split(",", 1)
    if ";base64" not in header:
        raise ValueError("n2 screenshots must use base64 data URLs")
    return base64.b64decode(encoded), header[5:].split(";", 1)[0]


def _data_url_media_type(url: str) -> str:
    if not isinstance(url, str) or not url.startswith("data:") or "," not in url:
        raise ValueError("n2 screenshots must be base64 data URLs")
    header = url.split(",", 1)[0]
    if ";base64" not in header:
        raise ValueError("n2 screenshots must use base64 data URLs")
    return header[5:].split(";", 1)[0]

_data_url_media_type was added right alongside _decode_data_url in #374 (the just-merged "send the whole screenshot history" fix) as a deliberate perf optimization — reading the media type off the header without base64-decoding the payload, since a request now walks every frame each step. But it duplicated _decode_data_url's validation and header-parsing wholesale instead of factoring it out.

Change

_decode_data_url now calls _data_url_media_type for validation + media-type extraction, then does only its own base64 decode:

def _decode_data_url(url: str) -> "tuple[bytes, str]":
    media_type = _data_url_media_type(url)
    _, encoded = url.split(",", 1)
    return base64.b64decode(encoded), media_type

One definition of what a valid n2 screenshot data URL looks like, instead of two that have to be kept in sync by hand.

Why it's safe

  • Both functions are module-private (_-prefixed, not exported from yutori.navigator or documented in api.md) — no public API surface touches this.
  • Same exceptions (ValueError, same messages) raised under the same conditions, same return values in both success and failure cases — I traced every branch by hand.
  • _data_url_media_type's whole reason to exist (avoid decoding the payload) is preserved: _decode_data_url still does exactly one base64 decode, no more.
  • Full non-slow suite passes unmodified: 914 passed, 3 skipped. The n2-specific suites (test_navigator_n2.py, test_navigator_n2_compaction.py, test_navigator_n2_harness.py, 123 tests) also pass directly.
  • ruff check / ruff format --check clean on the touched file.

One file, 13/13 lines changed, no behavior change.

Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01DvN8Ceba13sWCcsPbFmBUP

🤖 Generated with Claude Code

https://claude.ai/code/session_01DvN8Ceba13sWCcsPbFmBUP


Generated by Claude Code


Note

Low Risk
Internal refactor in a single private helper module with no public API or logic change beyond deduplication.

Overview
Refactors n2 screenshot data-URL handling so validation and media-type parsing live in one place instead of two copies that had to stay in sync.

_decode_data_url now delegates to _data_url_media_type for the shared checks and header parsing, then only base64-decodes the payload. _data_url_media_type is defined first and still avoids decoding when callers (e.g. prepare_n2_image_data_url) only need the format for a pass-through check.

No intended behavior change: same ValueError messages, same return shapes, and the decode-once path for full reads is unchanged.

Reviewed by Cursor Bugbot for commit baceed4. Bugbot is set up for automated code reviews on this repo. Configure here.

_decode_data_url and _data_url_media_type each hand-rolled the identical
"data:...,...;base64..." validation and the same header[5:].split(";", 1)[0]
media-type extraction -- the second copy was added right alongside the first
in #374 (prune-to-budget) without noticing the first already existed a few
lines up. _decode_data_url now calls _data_url_media_type for validation and
media-type extraction, then does only its own base64 decode, so there is one
definition of what a valid n2 screenshot data URL looks like.

No public behavior change: both functions are module-private (not exported
from yutori.navigator), same exceptions raised under the same conditions,
same return values. Full non-slow suite (914 passed, 3 skipped) and the n2
payload/harness/compaction suites pass unmodified; ruff check/format clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DvN8Ceba13sWCcsPbFmBUP
@dhruvbatra
dhruvbatra merged commit 3705b39 into main Sep 4, 2026
16 checks passed
@dhruvbatra
dhruvbatra deleted the claude/cool-brahmagupta-paer04 branch September 4, 2026 06:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants