Skip to content

fix(install.sh): enforce 60s smoke-test timeout; don't misreport Ctrl+C as failure#3181

Open
xiaoyaoqilan wants to merge 1 commit into
dimensionalOS:mainfrom
xiaoyaoqilan:fix/install-sh-smoke-test-timeout
Open

fix(install.sh): enforce 60s smoke-test timeout; don't misreport Ctrl+C as failure#3181
xiaoyaoqilan wants to merge 1 commit into
dimensionalOS:mainfrom
xiaoyaoqilan:fix/install-sh-smoke-test-timeout

Conversation

@xiaoyaoqilan

Copy link
Copy Markdown
Contributor

Fixes #3017.

Bug 1 — the 60s timeout never existed. The smoke-test prompt advertises 'starts unitree-go2 for 60s' and the result handler even checks for exit code 124 (what timeout returns), but the command was launched directly with $cmd & and never wrapped in timeout. So it ran indefinitely.

Bug 2 — Ctrl+C reported 'installation failed'. A bare wait $pid let the global installer INT/EXIT traps treat a smoke-test interruption as a full install failure.

Fix

  • Run the smoke test via timeout 60 (preferred). When coreutils timeout is unavailable (e.g. stock macOS), fall back to a portable sleep 60; kill watchdog, so the 60s cap is real on every platform.
  • Install a local INT trap around the wait that forwards Ctrl+C to the child and reports 'stopped by user', then restores the original handler — the installer no longer aborts and prints 'installation failed'.
  • Exit-code handling now treats 124/143 (timeout/our SIGTERM) as 'ran 60s without crash', 130/137 as 'stopped by user', 0 as 'completed'.

Validated with bash -n.

… Ctrl+C as failure

Resolves dimensionalOS#3017.

- Wrap the post-install smoke test in `timeout 60` (with a portable
  sleep+kill watchdog fallback for systems without coreutils `timeout`,
  e.g. stock macOS) so the advertised '60s' cap is actually honored.
- Add a local INT trap around the wait so Ctrl+C stops only the smoke
  test and reports 'stopped by user' instead of aborting the installer
  and printing 'installation failed'.
@greptile-apps

greptile-apps Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a 60-second smoke-test limit and localized interruption handling.

  • Uses coreutils timeout when available and a sleep/SIGTERM watchdog otherwise.
  • Temporarily forwards Ctrl+C to the smoke-test process and restores the installer’s original handler.
  • Classifies timeout, interruption, completion, and unexpected exit statuses separately.

Confidence Score: 2/5

The PR should not merge until direct installer execution is restored and smoke-test paths are passed safely to the child shell.

The checked-in installer is no longer executable, and valid project directories containing an apostrophe break or alter the generated smoke-test command.

Files Needing Attention: scripts/install.sh

Important Files Changed

Filename Overview
scripts/install.sh Adds bounded smoke-test and interrupt handling, but removes executable mode and constructs unsafe shell source from supported installation paths.

Sequence Diagram

sequenceDiagram
    participant Installer
    participant Wrapper as timeout / watchdog
    participant Smoke as DimOS smoke test
    Installer->>Wrapper: launch with 60s limit
    Wrapper->>Smoke: start unitree-go2
    alt Ctrl+C
        Installer->>Wrapper: SIGINT
    else 60 seconds elapsed
        Wrapper->>Smoke: SIGTERM
    end
    Wrapper-->>Installer: exit status
    Installer->>Installer: classify result and restore INT trap
Loading

Comments Outside Diff (1)

  1. scripts/install.sh, line 1 (link)

    P1 Installer loses executable permission

    When users or automation invoke ./scripts/install.sh directly, the new 100644 file mode makes the operating system reject the invocation with “Permission denied,” preventing installation from starting.

    Knowledge Base Used: Build, CI, and Packaging

Reviews (1): Last reviewed commit: "fix(install.sh): enforce 60s smoke-test ..." | Re-trigger Greptile

Comment thread scripts/install.sh
Comment on lines +894 to +896
timeout 60 bash -c "cd '$dir'; source '$venv'; $cmd" &
else
bash -c "cd '$dir'; source '$venv'; $cmd" &

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Paths alter generated shell syntax

When a supported project directory supplied through DIMOS_PROJECT_DIR, --project-dir, or the interactive prompt contains a single quote, embedding it in bash -c "cd '$dir'; source '$venv'; $cmd" terminates the quoting and treats part of the path as shell syntax, causing the smoke test to fail or execute unintended commands.

Knowledge Base Used: Build, CI, and Packaging

@codecov

codecov Bot commented Jul 25, 2026

Copy link
Copy Markdown

❌ 12 Tests Failed:

Tests completed Failed Passed Skipped
3238 12 3226 40
View the top 3 failed test(s) by shortest run time
dimos.web.relay_bridge.test_relay_e2e::test_large_frame_1mib
Stack Traces | 0.001s run time
def ensure_deno() -> str:
        which = shutil.which("deno")
        if which:
            return which
    
        exe_name = "deno.exe" if platform.system() == "Windows" else "deno"
        deno_dir = STATE_DIR / "deno" / DENO_VERSION
        deno_path = deno_dir / exe_name
        if deno_path.exists():
            return str(deno_path)
    
        triple = _deno_triple()
        url = f"https://github..../deno/releases/download/{DENO_VERSION}/deno-{triple}.zip"
        logger.info(f"Downloading deno {DENO_VERSION} from {url}")
        deno_dir.mkdir(parents=True, exist_ok=True)
        try:
>           _require_immutable_release()

deno_dir   = PosixPath('.../dimos/deno/v2.6.10')
deno_path  = PosixPath('.../dimos/deno/v2.6.10/deno')
exe_name   = 'deno'
triple     = 'x86_64-unknown-linux-gnu'
url        = 'https://github..../deno/releases/download/v2.6.10/deno-x86_64-unknown-linux-gnu.zip'
which      = None

dimos/utils/deno.py:51: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
dimos/utils/deno.py:80: in _require_immutable_release
    with urllib.request.urlopen(url, timeout=60) as resp:
        url        = 'https://api.github..../releases/tags/v2.6.10'
................../usr/lib/python3.12/urllib/request.py:215: in urlopen
    return opener.open(url, data, timeout)
        cadefault  = False
        cafile     = None
        capath     = None
        context    = None
        data       = None
        opener     = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
        timeout    = 60
        url        = 'https://api.github..../releases/tags/v2.6.10'
................../usr/lib/python3.12/urllib/request.py:521: in open
    response = meth(req, response)
        data       = None
        fullurl    = 'https://api.github..../releases/tags/v2.6.10'
        meth       = <bound method HTTPErrorProcessor.http_response of <urllib.request.HTTPErrorProcessor object at 0x7fc0e45b4080>>
        meth_name  = 'https_response'
        processor  = <urllib.request.HTTPErrorProcessor object at 0x7fc0e45b4080>
        protocol   = 'https'
        req        = <urllib.request.Request object at 0x7fc1150e7da0>
        response   = <http.client.HTTPResponse object at 0x7fc0e45e8b20>
        self       = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
        timeout    = 60
................../usr/lib/python3.12/urllib/request.py:630: in http_response
    response = self.parent.error(
        code       = 403
        hdrs       = <http.client.HTTPMessage object at 0x7fc0e45b75f0>
        msg        = 'rate limit exceeded'
        request    = <urllib.request.Request object at 0x7fc1150e7da0>
        response   = <http.client.HTTPResponse object at 0x7fc0e45e8b20>
        self       = <urllib.request.HTTPErrorProcessor object at 0x7fc0e45b4080>
................../usr/lib/python3.12/urllib/request.py:559: in error
    return self._call_chain(*args)
        args       = ({'default': [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>], 301: [<urllib.request.HTTPRedirectHa...ult', <urllib.request.Request object at 0x7fc1150e7da0>, <http.client.HTTPResponse object at 0x7fc0e45e8b20>, 403, ...)
        dict       = {'default': [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>], 301: [<urllib.request.HTTPRedirectHan...PRedirectHandler object at 0x7fc0e45b7770>], 303: [<urllib.request.HTTPRedirectHandler object at 0x7fc0e45b7770>], ...}
        http_err   = 1
        meth_name  = 'http_error_403'
        orig_args  = (<urllib.request.Request object at 0x7fc1150e7da0>, <http.client.HTTPResponse object at 0x7fc0e45e8b20>, 403, 'rate limit exceeded', <http.client.HTTPMessage object at 0x7fc0e45b75f0>)
        proto      = 403
        result     = None
        self       = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
................../usr/lib/python3.12/urllib/request.py:492: in _call_chain
    result = func(*args)
        args       = (<urllib.request.Request object at 0x7fc1150e7da0>, <http.client.HTTPResponse object at 0x7fc0e45e8b20>, 403, 'rate limit exceeded', <http.client.HTTPMessage object at 0x7fc0e45b75f0>)
        chain      = {'default': [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>], 301: [<urllib.request.HTTPRedirectHan...PRedirectHandler object at 0x7fc0e45b7770>], 303: [<urllib.request.HTTPRedirectHandler object at 0x7fc0e45b7770>], ...}
        func       = <bound method HTTPDefaultErrorHandler.http_error_default of <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>>
        handler    = <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>
        handlers   = [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>]
        kind       = 'default'
        meth_name  = 'http_error_default'
        self       = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>
req = <urllib.request.Request object at 0x7fc1150e7da0>
fp = <http.client.HTTPResponse object at 0x7fc0e45e8b20>, code = 403
msg = 'rate limit exceeded'
hdrs = <http.client.HTTPMessage object at 0x7fc0e45b75f0>

    def http_error_default(self, req, fp, code, msg, hdrs):
>       raise HTTPError(req.full_url, code, msg, hdrs, fp)
E       urllib.error.HTTPError: HTTP Error 403: rate limit exceeded

code       = 403
fp         = <http.client.HTTPResponse object at 0x7fc0e45e8b20>
hdrs       = <http.client.HTTPMessage object at 0x7fc0e45b75f0>
msg        = 'rate limit exceeded'
req        = <urllib.request.Request object at 0x7fc1150e7da0>
self       = <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>

................../usr/lib/python3.12/urllib/request.py:639: HTTPError

The above exception was the direct cause of the following exception:

    @pytest.fixture(scope="module")
    def relay() -> Iterator[RelayReadyInfo]:
        process = RelayProcess()
        try:
>           yield process.start()

process    = <dimos.web.relay_bridge.relay_process.RelayProcess object at 0x7fc0e45b6690>

.../web/relay_bridge/test_relay_e2e.py:40: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../web/relay_bridge/relay_process.py:73: in start
    deno = ensure_deno()
        self       = <dimos.web.relay_bridge.relay_process.RelayProcess object at 0x7fc0e45b6690>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def ensure_deno() -> str:
        which = shutil.which("deno")
        if which:
            return which
    
        exe_name = "deno.exe" if platform.system() == "Windows" else "deno"
        deno_dir = STATE_DIR / "deno" / DENO_VERSION
        deno_path = deno_dir / exe_name
        if deno_path.exists():
            return str(deno_path)
    
        triple = _deno_triple()
        url = f"https://github..../deno/releases/download/{DENO_VERSION}/deno-{triple}.zip"
        logger.info(f"Downloading deno {DENO_VERSION} from {url}")
        deno_dir.mkdir(parents=True, exist_ok=True)
        try:
            _require_immutable_release()
            with tempfile.TemporaryDirectory(dir=str(deno_dir.parent)) as tmp:
                tmp_path = Path(tmp)
                zip_path = tmp_path / "deno.zip"
                with urllib.request.urlopen(url, timeout=60) as resp, open(zip_path, "wb") as f:
                    shutil.copyfileobj(resp, f)
                with zipfile.ZipFile(zip_path) as z:
                    z.extractall(tmp_path)
                extracted = tmp_path / exe_name
                if not extracted.exists():
                    raise RuntimeError(f"deno binary not found in archive from {url}")
                extracted.chmod(extracted.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
                extracted.replace(deno_path)
        except Exception as e:
>           raise RuntimeError(
                f"deno is required. Auto-download failed: {e}. Install manually from https://deno.com/"
            ) from e
E           RuntimeError: deno is required. Auto-download failed: HTTP Error 403: rate limit exceeded. Install manually from https://deno.com/

deno_dir   = PosixPath('.../dimos/deno/v2.6.10')
deno_path  = PosixPath('.../dimos/deno/v2.6.10/deno')
exe_name   = 'deno'
triple     = 'x86_64-unknown-linux-gnu'
url        = 'https://github..../deno/releases/download/v2.6.10/deno-x86_64-unknown-linux-gnu.zip'
which      = None

dimos/utils/deno.py:65: RuntimeError
dimos.web.relay_bridge.test_relay_e2e::test_latest_channel_newest_wins
Stack Traces | 0.001s run time
def ensure_deno() -> str:
        which = shutil.which("deno")
        if which:
            return which
    
        exe_name = "deno.exe" if platform.system() == "Windows" else "deno"
        deno_dir = STATE_DIR / "deno" / DENO_VERSION
        deno_path = deno_dir / exe_name
        if deno_path.exists():
            return str(deno_path)
    
        triple = _deno_triple()
        url = f"https://github..../deno/releases/download/{DENO_VERSION}/deno-{triple}.zip"
        logger.info(f"Downloading deno {DENO_VERSION} from {url}")
        deno_dir.mkdir(parents=True, exist_ok=True)
        try:
>           _require_immutable_release()

deno_dir   = PosixPath('.../dimos/deno/v2.6.10')
deno_path  = PosixPath('.../dimos/deno/v2.6.10/deno')
exe_name   = 'deno'
triple     = 'x86_64-unknown-linux-gnu'
url        = 'https://github..../deno/releases/download/v2.6.10/deno-x86_64-unknown-linux-gnu.zip'
which      = None

dimos/utils/deno.py:51: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
dimos/utils/deno.py:80: in _require_immutable_release
    with urllib.request.urlopen(url, timeout=60) as resp:
        url        = 'https://api.github..../releases/tags/v2.6.10'
................../usr/lib/python3.12/urllib/request.py:215: in urlopen
    return opener.open(url, data, timeout)
        cadefault  = False
        cafile     = None
        capath     = None
        context    = None
        data       = None
        opener     = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
        timeout    = 60
        url        = 'https://api.github..../releases/tags/v2.6.10'
................../usr/lib/python3.12/urllib/request.py:521: in open
    response = meth(req, response)
        data       = None
        fullurl    = 'https://api.github..../releases/tags/v2.6.10'
        meth       = <bound method HTTPErrorProcessor.http_response of <urllib.request.HTTPErrorProcessor object at 0x7fc0e45b4080>>
        meth_name  = 'https_response'
        processor  = <urllib.request.HTTPErrorProcessor object at 0x7fc0e45b4080>
        protocol   = 'https'
        req        = <urllib.request.Request object at 0x7fc1150e7da0>
        response   = <http.client.HTTPResponse object at 0x7fc0e45e8b20>
        self       = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
        timeout    = 60
................../usr/lib/python3.12/urllib/request.py:630: in http_response
    response = self.parent.error(
        code       = 403
        hdrs       = <http.client.HTTPMessage object at 0x7fc0e45b75f0>
        msg        = 'rate limit exceeded'
        request    = <urllib.request.Request object at 0x7fc1150e7da0>
        response   = <http.client.HTTPResponse object at 0x7fc0e45e8b20>
        self       = <urllib.request.HTTPErrorProcessor object at 0x7fc0e45b4080>
................../usr/lib/python3.12/urllib/request.py:559: in error
    return self._call_chain(*args)
        args       = ({'default': [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>], 301: [<urllib.request.HTTPRedirectHa...ult', <urllib.request.Request object at 0x7fc1150e7da0>, <http.client.HTTPResponse object at 0x7fc0e45e8b20>, 403, ...)
        dict       = {'default': [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>], 301: [<urllib.request.HTTPRedirectHan...PRedirectHandler object at 0x7fc0e45b7770>], 303: [<urllib.request.HTTPRedirectHandler object at 0x7fc0e45b7770>], ...}
        http_err   = 1
        meth_name  = 'http_error_403'
        orig_args  = (<urllib.request.Request object at 0x7fc1150e7da0>, <http.client.HTTPResponse object at 0x7fc0e45e8b20>, 403, 'rate limit exceeded', <http.client.HTTPMessage object at 0x7fc0e45b75f0>)
        proto      = 403
        result     = None
        self       = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
................../usr/lib/python3.12/urllib/request.py:492: in _call_chain
    result = func(*args)
        args       = (<urllib.request.Request object at 0x7fc1150e7da0>, <http.client.HTTPResponse object at 0x7fc0e45e8b20>, 403, 'rate limit exceeded', <http.client.HTTPMessage object at 0x7fc0e45b75f0>)
        chain      = {'default': [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>], 301: [<urllib.request.HTTPRedirectHan...PRedirectHandler object at 0x7fc0e45b7770>], 303: [<urllib.request.HTTPRedirectHandler object at 0x7fc0e45b7770>], ...}
        func       = <bound method HTTPDefaultErrorHandler.http_error_default of <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>>
        handler    = <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>
        handlers   = [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>]
        kind       = 'default'
        meth_name  = 'http_error_default'
        self       = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>
req = <urllib.request.Request object at 0x7fc1150e7da0>
fp = <http.client.HTTPResponse object at 0x7fc0e45e8b20>, code = 403
msg = 'rate limit exceeded'
hdrs = <http.client.HTTPMessage object at 0x7fc0e45b75f0>

    def http_error_default(self, req, fp, code, msg, hdrs):
>       raise HTTPError(req.full_url, code, msg, hdrs, fp)
E       urllib.error.HTTPError: HTTP Error 403: rate limit exceeded

code       = 403
fp         = <http.client.HTTPResponse object at 0x7fc0e45e8b20>
hdrs       = <http.client.HTTPMessage object at 0x7fc0e45b75f0>
msg        = 'rate limit exceeded'
req        = <urllib.request.Request object at 0x7fc1150e7da0>
self       = <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>

................../usr/lib/python3.12/urllib/request.py:639: HTTPError

The above exception was the direct cause of the following exception:

    @pytest.fixture(scope="module")
    def relay() -> Iterator[RelayReadyInfo]:
        process = RelayProcess()
        try:
>           yield process.start()

process    = <dimos.web.relay_bridge.relay_process.RelayProcess object at 0x7fc0e45b6690>

.../web/relay_bridge/test_relay_e2e.py:40: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../web/relay_bridge/relay_process.py:73: in start
    deno = ensure_deno()
        self       = <dimos.web.relay_bridge.relay_process.RelayProcess object at 0x7fc0e45b6690>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def ensure_deno() -> str:
        which = shutil.which("deno")
        if which:
            return which
    
        exe_name = "deno.exe" if platform.system() == "Windows" else "deno"
        deno_dir = STATE_DIR / "deno" / DENO_VERSION
        deno_path = deno_dir / exe_name
        if deno_path.exists():
            return str(deno_path)
    
        triple = _deno_triple()
        url = f"https://github..../deno/releases/download/{DENO_VERSION}/deno-{triple}.zip"
        logger.info(f"Downloading deno {DENO_VERSION} from {url}")
        deno_dir.mkdir(parents=True, exist_ok=True)
        try:
            _require_immutable_release()
            with tempfile.TemporaryDirectory(dir=str(deno_dir.parent)) as tmp:
                tmp_path = Path(tmp)
                zip_path = tmp_path / "deno.zip"
                with urllib.request.urlopen(url, timeout=60) as resp, open(zip_path, "wb") as f:
                    shutil.copyfileobj(resp, f)
                with zipfile.ZipFile(zip_path) as z:
                    z.extractall(tmp_path)
                extracted = tmp_path / exe_name
                if not extracted.exists():
                    raise RuntimeError(f"deno binary not found in archive from {url}")
                extracted.chmod(extracted.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
                extracted.replace(deno_path)
        except Exception as e:
>           raise RuntimeError(
                f"deno is required. Auto-download failed: {e}. Install manually from https://deno.com/"
            ) from e
E           RuntimeError: deno is required. Auto-download failed: HTTP Error 403: rate limit exceeded. Install manually from https://deno.com/

deno_dir   = PosixPath('.../dimos/deno/v2.6.10')
deno_path  = PosixPath('.../dimos/deno/v2.6.10/deno')
exe_name   = 'deno'
triple     = 'x86_64-unknown-linux-gnu'
url        = 'https://github..../deno/releases/download/v2.6.10/deno-x86_64-unknown-linux-gnu.zip'
which      = None

dimos/utils/deno.py:65: RuntimeError
dimos.web.relay_bridge.test_relay_e2e::test_latest_writer_resets_stale_stream
Stack Traces | 0.001s run time
def ensure_deno() -> str:
        which = shutil.which("deno")
        if which:
            return which
    
        exe_name = "deno.exe" if platform.system() == "Windows" else "deno"
        deno_dir = STATE_DIR / "deno" / DENO_VERSION
        deno_path = deno_dir / exe_name
        if deno_path.exists():
            return str(deno_path)
    
        triple = _deno_triple()
        url = f"https://github..../deno/releases/download/{DENO_VERSION}/deno-{triple}.zip"
        logger.info(f"Downloading deno {DENO_VERSION} from {url}")
        deno_dir.mkdir(parents=True, exist_ok=True)
        try:
>           _require_immutable_release()

deno_dir   = PosixPath('.../dimos/deno/v2.6.10')
deno_path  = PosixPath('.../dimos/deno/v2.6.10/deno')
exe_name   = 'deno'
triple     = 'x86_64-unknown-linux-gnu'
url        = 'https://github..../deno/releases/download/v2.6.10/deno-x86_64-unknown-linux-gnu.zip'
which      = None

dimos/utils/deno.py:51: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
dimos/utils/deno.py:80: in _require_immutable_release
    with urllib.request.urlopen(url, timeout=60) as resp:
        url        = 'https://api.github..../releases/tags/v2.6.10'
................../usr/lib/python3.12/urllib/request.py:215: in urlopen
    return opener.open(url, data, timeout)
        cadefault  = False
        cafile     = None
        capath     = None
        context    = None
        data       = None
        opener     = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
        timeout    = 60
        url        = 'https://api.github..../releases/tags/v2.6.10'
................../usr/lib/python3.12/urllib/request.py:521: in open
    response = meth(req, response)
        data       = None
        fullurl    = 'https://api.github..../releases/tags/v2.6.10'
        meth       = <bound method HTTPErrorProcessor.http_response of <urllib.request.HTTPErrorProcessor object at 0x7fc0e45b4080>>
        meth_name  = 'https_response'
        processor  = <urllib.request.HTTPErrorProcessor object at 0x7fc0e45b4080>
        protocol   = 'https'
        req        = <urllib.request.Request object at 0x7fc1150e7da0>
        response   = <http.client.HTTPResponse object at 0x7fc0e45e8b20>
        self       = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
        timeout    = 60
................../usr/lib/python3.12/urllib/request.py:630: in http_response
    response = self.parent.error(
        code       = 403
        hdrs       = <http.client.HTTPMessage object at 0x7fc0e45b75f0>
        msg        = 'rate limit exceeded'
        request    = <urllib.request.Request object at 0x7fc1150e7da0>
        response   = <http.client.HTTPResponse object at 0x7fc0e45e8b20>
        self       = <urllib.request.HTTPErrorProcessor object at 0x7fc0e45b4080>
................../usr/lib/python3.12/urllib/request.py:559: in error
    return self._call_chain(*args)
        args       = ({'default': [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>], 301: [<urllib.request.HTTPRedirectHa...ult', <urllib.request.Request object at 0x7fc1150e7da0>, <http.client.HTTPResponse object at 0x7fc0e45e8b20>, 403, ...)
        dict       = {'default': [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>], 301: [<urllib.request.HTTPRedirectHan...PRedirectHandler object at 0x7fc0e45b7770>], 303: [<urllib.request.HTTPRedirectHandler object at 0x7fc0e45b7770>], ...}
        http_err   = 1
        meth_name  = 'http_error_403'
        orig_args  = (<urllib.request.Request object at 0x7fc1150e7da0>, <http.client.HTTPResponse object at 0x7fc0e45e8b20>, 403, 'rate limit exceeded', <http.client.HTTPMessage object at 0x7fc0e45b75f0>)
        proto      = 403
        result     = None
        self       = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
................../usr/lib/python3.12/urllib/request.py:492: in _call_chain
    result = func(*args)
        args       = (<urllib.request.Request object at 0x7fc1150e7da0>, <http.client.HTTPResponse object at 0x7fc0e45e8b20>, 403, 'rate limit exceeded', <http.client.HTTPMessage object at 0x7fc0e45b75f0>)
        chain      = {'default': [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>], 301: [<urllib.request.HTTPRedirectHan...PRedirectHandler object at 0x7fc0e45b7770>], 303: [<urllib.request.HTTPRedirectHandler object at 0x7fc0e45b7770>], ...}
        func       = <bound method HTTPDefaultErrorHandler.http_error_default of <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>>
        handler    = <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>
        handlers   = [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>]
        kind       = 'default'
        meth_name  = 'http_error_default'
        self       = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>
req = <urllib.request.Request object at 0x7fc1150e7da0>
fp = <http.client.HTTPResponse object at 0x7fc0e45e8b20>, code = 403
msg = 'rate limit exceeded'
hdrs = <http.client.HTTPMessage object at 0x7fc0e45b75f0>

    def http_error_default(self, req, fp, code, msg, hdrs):
>       raise HTTPError(req.full_url, code, msg, hdrs, fp)
E       urllib.error.HTTPError: HTTP Error 403: rate limit exceeded

code       = 403
fp         = <http.client.HTTPResponse object at 0x7fc0e45e8b20>
hdrs       = <http.client.HTTPMessage object at 0x7fc0e45b75f0>
msg        = 'rate limit exceeded'
req        = <urllib.request.Request object at 0x7fc1150e7da0>
self       = <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>

................../usr/lib/python3.12/urllib/request.py:639: HTTPError

The above exception was the direct cause of the following exception:

    @pytest.fixture(scope="module")
    def relay() -> Iterator[RelayReadyInfo]:
        process = RelayProcess()
        try:
>           yield process.start()

process    = <dimos.web.relay_bridge.relay_process.RelayProcess object at 0x7fc0e45b6690>

.../web/relay_bridge/test_relay_e2e.py:40: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../web/relay_bridge/relay_process.py:73: in start
    deno = ensure_deno()
        self       = <dimos.web.relay_bridge.relay_process.RelayProcess object at 0x7fc0e45b6690>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def ensure_deno() -> str:
        which = shutil.which("deno")
        if which:
            return which
    
        exe_name = "deno.exe" if platform.system() == "Windows" else "deno"
        deno_dir = STATE_DIR / "deno" / DENO_VERSION
        deno_path = deno_dir / exe_name
        if deno_path.exists():
            return str(deno_path)
    
        triple = _deno_triple()
        url = f"https://github..../deno/releases/download/{DENO_VERSION}/deno-{triple}.zip"
        logger.info(f"Downloading deno {DENO_VERSION} from {url}")
        deno_dir.mkdir(parents=True, exist_ok=True)
        try:
            _require_immutable_release()
            with tempfile.TemporaryDirectory(dir=str(deno_dir.parent)) as tmp:
                tmp_path = Path(tmp)
                zip_path = tmp_path / "deno.zip"
                with urllib.request.urlopen(url, timeout=60) as resp, open(zip_path, "wb") as f:
                    shutil.copyfileobj(resp, f)
                with zipfile.ZipFile(zip_path) as z:
                    z.extractall(tmp_path)
                extracted = tmp_path / exe_name
                if not extracted.exists():
                    raise RuntimeError(f"deno binary not found in archive from {url}")
                extracted.chmod(extracted.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
                extracted.replace(deno_path)
        except Exception as e:
>           raise RuntimeError(
                f"deno is required. Auto-download failed: {e}. Install manually from https://deno.com/"
            ) from e
E           RuntimeError: deno is required. Auto-download failed: HTTP Error 403: rate limit exceeded. Install manually from https://deno.com/

deno_dir   = PosixPath('.../dimos/deno/v2.6.10')
deno_path  = PosixPath('.../dimos/deno/v2.6.10/deno')
exe_name   = 'deno'
triple     = 'x86_64-unknown-linux-gnu'
url        = 'https://github..../deno/releases/download/v2.6.10/deno-x86_64-unknown-linux-gnu.zip'
which      = None

dimos/utils/deno.py:65: RuntimeError
dimos.web.relay_bridge.test_relay_e2e::test_malformed_robot_frame_is_dropped
Stack Traces | 0.001s run time
def ensure_deno() -> str:
        which = shutil.which("deno")
        if which:
            return which
    
        exe_name = "deno.exe" if platform.system() == "Windows" else "deno"
        deno_dir = STATE_DIR / "deno" / DENO_VERSION
        deno_path = deno_dir / exe_name
        if deno_path.exists():
            return str(deno_path)
    
        triple = _deno_triple()
        url = f"https://github..../deno/releases/download/{DENO_VERSION}/deno-{triple}.zip"
        logger.info(f"Downloading deno {DENO_VERSION} from {url}")
        deno_dir.mkdir(parents=True, exist_ok=True)
        try:
>           _require_immutable_release()

deno_dir   = PosixPath('.../dimos/deno/v2.6.10')
deno_path  = PosixPath('.../dimos/deno/v2.6.10/deno')
exe_name   = 'deno'
triple     = 'x86_64-unknown-linux-gnu'
url        = 'https://github..../deno/releases/download/v2.6.10/deno-x86_64-unknown-linux-gnu.zip'
which      = None

dimos/utils/deno.py:51: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
dimos/utils/deno.py:80: in _require_immutable_release
    with urllib.request.urlopen(url, timeout=60) as resp:
        url        = 'https://api.github..../releases/tags/v2.6.10'
................../usr/lib/python3.12/urllib/request.py:215: in urlopen
    return opener.open(url, data, timeout)
        cadefault  = False
        cafile     = None
        capath     = None
        context    = None
        data       = None
        opener     = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
        timeout    = 60
        url        = 'https://api.github..../releases/tags/v2.6.10'
................../usr/lib/python3.12/urllib/request.py:521: in open
    response = meth(req, response)
        data       = None
        fullurl    = 'https://api.github..../releases/tags/v2.6.10'
        meth       = <bound method HTTPErrorProcessor.http_response of <urllib.request.HTTPErrorProcessor object at 0x7fc0e45b4080>>
        meth_name  = 'https_response'
        processor  = <urllib.request.HTTPErrorProcessor object at 0x7fc0e45b4080>
        protocol   = 'https'
        req        = <urllib.request.Request object at 0x7fc1150e7da0>
        response   = <http.client.HTTPResponse object at 0x7fc0e45e8b20>
        self       = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
        timeout    = 60
................../usr/lib/python3.12/urllib/request.py:630: in http_response
    response = self.parent.error(
        code       = 403
        hdrs       = <http.client.HTTPMessage object at 0x7fc0e45b75f0>
        msg        = 'rate limit exceeded'
        request    = <urllib.request.Request object at 0x7fc1150e7da0>
        response   = <http.client.HTTPResponse object at 0x7fc0e45e8b20>
        self       = <urllib.request.HTTPErrorProcessor object at 0x7fc0e45b4080>
................../usr/lib/python3.12/urllib/request.py:559: in error
    return self._call_chain(*args)
        args       = ({'default': [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>], 301: [<urllib.request.HTTPRedirectHa...ult', <urllib.request.Request object at 0x7fc1150e7da0>, <http.client.HTTPResponse object at 0x7fc0e45e8b20>, 403, ...)
        dict       = {'default': [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>], 301: [<urllib.request.HTTPRedirectHan...PRedirectHandler object at 0x7fc0e45b7770>], 303: [<urllib.request.HTTPRedirectHandler object at 0x7fc0e45b7770>], ...}
        http_err   = 1
        meth_name  = 'http_error_403'
        orig_args  = (<urllib.request.Request object at 0x7fc1150e7da0>, <http.client.HTTPResponse object at 0x7fc0e45e8b20>, 403, 'rate limit exceeded', <http.client.HTTPMessage object at 0x7fc0e45b75f0>)
        proto      = 403
        result     = None
        self       = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
................../usr/lib/python3.12/urllib/request.py:492: in _call_chain
    result = func(*args)
        args       = (<urllib.request.Request object at 0x7fc1150e7da0>, <http.client.HTTPResponse object at 0x7fc0e45e8b20>, 403, 'rate limit exceeded', <http.client.HTTPMessage object at 0x7fc0e45b75f0>)
        chain      = {'default': [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>], 301: [<urllib.request.HTTPRedirectHan...PRedirectHandler object at 0x7fc0e45b7770>], 303: [<urllib.request.HTTPRedirectHandler object at 0x7fc0e45b7770>], ...}
        func       = <bound method HTTPDefaultErrorHandler.http_error_default of <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>>
        handler    = <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>
        handlers   = [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>]
        kind       = 'default'
        meth_name  = 'http_error_default'
        self       = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>
req = <urllib.request.Request object at 0x7fc1150e7da0>
fp = <http.client.HTTPResponse object at 0x7fc0e45e8b20>, code = 403
msg = 'rate limit exceeded'
hdrs = <http.client.HTTPMessage object at 0x7fc0e45b75f0>

    def http_error_default(self, req, fp, code, msg, hdrs):
>       raise HTTPError(req.full_url, code, msg, hdrs, fp)
E       urllib.error.HTTPError: HTTP Error 403: rate limit exceeded

code       = 403
fp         = <http.client.HTTPResponse object at 0x7fc0e45e8b20>
hdrs       = <http.client.HTTPMessage object at 0x7fc0e45b75f0>
msg        = 'rate limit exceeded'
req        = <urllib.request.Request object at 0x7fc1150e7da0>
self       = <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>

................../usr/lib/python3.12/urllib/request.py:639: HTTPError

The above exception was the direct cause of the following exception:

    @pytest.fixture(scope="module")
    def relay() -> Iterator[RelayReadyInfo]:
        process = RelayProcess()
        try:
>           yield process.start()

process    = <dimos.web.relay_bridge.relay_process.RelayProcess object at 0x7fc0e45b6690>

.../web/relay_bridge/test_relay_e2e.py:40: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../web/relay_bridge/relay_process.py:73: in start
    deno = ensure_deno()
        self       = <dimos.web.relay_bridge.relay_process.RelayProcess object at 0x7fc0e45b6690>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def ensure_deno() -> str:
        which = shutil.which("deno")
        if which:
            return which
    
        exe_name = "deno.exe" if platform.system() == "Windows" else "deno"
        deno_dir = STATE_DIR / "deno" / DENO_VERSION
        deno_path = deno_dir / exe_name
        if deno_path.exists():
            return str(deno_path)
    
        triple = _deno_triple()
        url = f"https://github..../deno/releases/download/{DENO_VERSION}/deno-{triple}.zip"
        logger.info(f"Downloading deno {DENO_VERSION} from {url}")
        deno_dir.mkdir(parents=True, exist_ok=True)
        try:
            _require_immutable_release()
            with tempfile.TemporaryDirectory(dir=str(deno_dir.parent)) as tmp:
                tmp_path = Path(tmp)
                zip_path = tmp_path / "deno.zip"
                with urllib.request.urlopen(url, timeout=60) as resp, open(zip_path, "wb") as f:
                    shutil.copyfileobj(resp, f)
                with zipfile.ZipFile(zip_path) as z:
                    z.extractall(tmp_path)
                extracted = tmp_path / exe_name
                if not extracted.exists():
                    raise RuntimeError(f"deno binary not found in archive from {url}")
                extracted.chmod(extracted.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
                extracted.replace(deno_path)
        except Exception as e:
>           raise RuntimeError(
                f"deno is required. Auto-download failed: {e}. Install manually from https://deno.com/"
            ) from e
E           RuntimeError: deno is required. Auto-download failed: HTTP Error 403: rate limit exceeded. Install manually from https://deno.com/

deno_dir   = PosixPath('.../dimos/deno/v2.6.10')
deno_path  = PosixPath('.../dimos/deno/v2.6.10/deno')
exe_name   = 'deno'
triple     = 'x86_64-unknown-linux-gnu'
url        = 'https://github..../deno/releases/download/v2.6.10/deno-x86_64-unknown-linux-gnu.zip'
which      = None

dimos/utils/deno.py:65: RuntimeError
dimos.web.relay_bridge.test_relay_e2e::test_reliable_channel_is_complete_and_intact
Stack Traces | 0.001s run time
def ensure_deno() -> str:
        which = shutil.which("deno")
        if which:
            return which
    
        exe_name = "deno.exe" if platform.system() == "Windows" else "deno"
        deno_dir = STATE_DIR / "deno" / DENO_VERSION
        deno_path = deno_dir / exe_name
        if deno_path.exists():
            return str(deno_path)
    
        triple = _deno_triple()
        url = f"https://github..../deno/releases/download/{DENO_VERSION}/deno-{triple}.zip"
        logger.info(f"Downloading deno {DENO_VERSION} from {url}")
        deno_dir.mkdir(parents=True, exist_ok=True)
        try:
>           _require_immutable_release()

deno_dir   = PosixPath('.../dimos/deno/v2.6.10')
deno_path  = PosixPath('.../dimos/deno/v2.6.10/deno')
exe_name   = 'deno'
triple     = 'x86_64-unknown-linux-gnu'
url        = 'https://github..../deno/releases/download/v2.6.10/deno-x86_64-unknown-linux-gnu.zip'
which      = None

dimos/utils/deno.py:51: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
dimos/utils/deno.py:80: in _require_immutable_release
    with urllib.request.urlopen(url, timeout=60) as resp:
        url        = 'https://api.github..../releases/tags/v2.6.10'
................../usr/lib/python3.12/urllib/request.py:215: in urlopen
    return opener.open(url, data, timeout)
        cadefault  = False
        cafile     = None
        capath     = None
        context    = None
        data       = None
        opener     = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
        timeout    = 60
        url        = 'https://api.github..../releases/tags/v2.6.10'
................../usr/lib/python3.12/urllib/request.py:521: in open
    response = meth(req, response)
        data       = None
        fullurl    = 'https://api.github..../releases/tags/v2.6.10'
        meth       = <bound method HTTPErrorProcessor.http_response of <urllib.request.HTTPErrorProcessor object at 0x7fc0e45b4080>>
        meth_name  = 'https_response'
        processor  = <urllib.request.HTTPErrorProcessor object at 0x7fc0e45b4080>
        protocol   = 'https'
        req        = <urllib.request.Request object at 0x7fc1150e7da0>
        response   = <http.client.HTTPResponse object at 0x7fc0e45e8b20>
        self       = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
        timeout    = 60
................../usr/lib/python3.12/urllib/request.py:630: in http_response
    response = self.parent.error(
        code       = 403
        hdrs       = <http.client.HTTPMessage object at 0x7fc0e45b75f0>
        msg        = 'rate limit exceeded'
        request    = <urllib.request.Request object at 0x7fc1150e7da0>
        response   = <http.client.HTTPResponse object at 0x7fc0e45e8b20>
        self       = <urllib.request.HTTPErrorProcessor object at 0x7fc0e45b4080>
................../usr/lib/python3.12/urllib/request.py:559: in error
    return self._call_chain(*args)
        args       = ({'default': [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>], 301: [<urllib.request.HTTPRedirectHa...ult', <urllib.request.Request object at 0x7fc1150e7da0>, <http.client.HTTPResponse object at 0x7fc0e45e8b20>, 403, ...)
        dict       = {'default': [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>], 301: [<urllib.request.HTTPRedirectHan...PRedirectHandler object at 0x7fc0e45b7770>], 303: [<urllib.request.HTTPRedirectHandler object at 0x7fc0e45b7770>], ...}
        http_err   = 1
        meth_name  = 'http_error_403'
        orig_args  = (<urllib.request.Request object at 0x7fc1150e7da0>, <http.client.HTTPResponse object at 0x7fc0e45e8b20>, 403, 'rate limit exceeded', <http.client.HTTPMessage object at 0x7fc0e45b75f0>)
        proto      = 403
        result     = None
        self       = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
................../usr/lib/python3.12/urllib/request.py:492: in _call_chain
    result = func(*args)
        args       = (<urllib.request.Request object at 0x7fc1150e7da0>, <http.client.HTTPResponse object at 0x7fc0e45e8b20>, 403, 'rate limit exceeded', <http.client.HTTPMessage object at 0x7fc0e45b75f0>)
        chain      = {'default': [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>], 301: [<urllib.request.HTTPRedirectHan...PRedirectHandler object at 0x7fc0e45b7770>], 303: [<urllib.request.HTTPRedirectHandler object at 0x7fc0e45b7770>], ...}
        func       = <bound method HTTPDefaultErrorHandler.http_error_default of <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>>
        handler    = <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>
        handlers   = [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>]
        kind       = 'default'
        meth_name  = 'http_error_default'
        self       = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>
req = <urllib.request.Request object at 0x7fc1150e7da0>
fp = <http.client.HTTPResponse object at 0x7fc0e45e8b20>, code = 403
msg = 'rate limit exceeded'
hdrs = <http.client.HTTPMessage object at 0x7fc0e45b75f0>

    def http_error_default(self, req, fp, code, msg, hdrs):
>       raise HTTPError(req.full_url, code, msg, hdrs, fp)
E       urllib.error.HTTPError: HTTP Error 403: rate limit exceeded

code       = 403
fp         = <http.client.HTTPResponse object at 0x7fc0e45e8b20>
hdrs       = <http.client.HTTPMessage object at 0x7fc0e45b75f0>
msg        = 'rate limit exceeded'
req        = <urllib.request.Request object at 0x7fc1150e7da0>
self       = <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>

................../usr/lib/python3.12/urllib/request.py:639: HTTPError

The above exception was the direct cause of the following exception:

    @pytest.fixture(scope="module")
    def relay() -> Iterator[RelayReadyInfo]:
        process = RelayProcess()
        try:
>           yield process.start()

process    = <dimos.web.relay_bridge.relay_process.RelayProcess object at 0x7fc0e45b6690>

.../web/relay_bridge/test_relay_e2e.py:40: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../web/relay_bridge/relay_process.py:73: in start
    deno = ensure_deno()
        self       = <dimos.web.relay_bridge.relay_process.RelayProcess object at 0x7fc0e45b6690>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def ensure_deno() -> str:
        which = shutil.which("deno")
        if which:
            return which
    
        exe_name = "deno.exe" if platform.system() == "Windows" else "deno"
        deno_dir = STATE_DIR / "deno" / DENO_VERSION
        deno_path = deno_dir / exe_name
        if deno_path.exists():
            return str(deno_path)
    
        triple = _deno_triple()
        url = f"https://github..../deno/releases/download/{DENO_VERSION}/deno-{triple}.zip"
        logger.info(f"Downloading deno {DENO_VERSION} from {url}")
        deno_dir.mkdir(parents=True, exist_ok=True)
        try:
            _require_immutable_release()
            with tempfile.TemporaryDirectory(dir=str(deno_dir.parent)) as tmp:
                tmp_path = Path(tmp)
                zip_path = tmp_path / "deno.zip"
                with urllib.request.urlopen(url, timeout=60) as resp, open(zip_path, "wb") as f:
                    shutil.copyfileobj(resp, f)
                with zipfile.ZipFile(zip_path) as z:
                    z.extractall(tmp_path)
                extracted = tmp_path / exe_name
                if not extracted.exists():
                    raise RuntimeError(f"deno binary not found in archive from {url}")
                extracted.chmod(extracted.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
                extracted.replace(deno_path)
        except Exception as e:
>           raise RuntimeError(
                f"deno is required. Auto-download failed: {e}. Install manually from https://deno.com/"
            ) from e
E           RuntimeError: deno is required. Auto-download failed: HTTP Error 403: rate limit exceeded. Install manually from https://deno.com/

deno_dir   = PosixPath('.../dimos/deno/v2.6.10')
deno_path  = PosixPath('.../dimos/deno/v2.6.10/deno')
exe_name   = 'deno'
triple     = 'x86_64-unknown-linux-gnu'
url        = 'https://github..../deno/releases/download/v2.6.10/deno-x86_64-unknown-linux-gnu.zip'
which      = None

dimos/utils/deno.py:65: RuntimeError
dimos.web.relay_bridge.test_relay_e2e::test_reset_burst_does_not_wedge_robot_leg
Stack Traces | 0.001s run time
def ensure_deno() -> str:
        which = shutil.which("deno")
        if which:
            return which
    
        exe_name = "deno.exe" if platform.system() == "Windows" else "deno"
        deno_dir = STATE_DIR / "deno" / DENO_VERSION
        deno_path = deno_dir / exe_name
        if deno_path.exists():
            return str(deno_path)
    
        triple = _deno_triple()
        url = f"https://github..../deno/releases/download/{DENO_VERSION}/deno-{triple}.zip"
        logger.info(f"Downloading deno {DENO_VERSION} from {url}")
        deno_dir.mkdir(parents=True, exist_ok=True)
        try:
>           _require_immutable_release()

deno_dir   = PosixPath('.../dimos/deno/v2.6.10')
deno_path  = PosixPath('.../dimos/deno/v2.6.10/deno')
exe_name   = 'deno'
triple     = 'x86_64-unknown-linux-gnu'
url        = 'https://github..../deno/releases/download/v2.6.10/deno-x86_64-unknown-linux-gnu.zip'
which      = None

dimos/utils/deno.py:51: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
dimos/utils/deno.py:80: in _require_immutable_release
    with urllib.request.urlopen(url, timeout=60) as resp:
        url        = 'https://api.github..../releases/tags/v2.6.10'
................../usr/lib/python3.12/urllib/request.py:215: in urlopen
    return opener.open(url, data, timeout)
        cadefault  = False
        cafile     = None
        capath     = None
        context    = None
        data       = None
        opener     = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
        timeout    = 60
        url        = 'https://api.github..../releases/tags/v2.6.10'
................../usr/lib/python3.12/urllib/request.py:521: in open
    response = meth(req, response)
        data       = None
        fullurl    = 'https://api.github..../releases/tags/v2.6.10'
        meth       = <bound method HTTPErrorProcessor.http_response of <urllib.request.HTTPErrorProcessor object at 0x7fc0e45b4080>>
        meth_name  = 'https_response'
        processor  = <urllib.request.HTTPErrorProcessor object at 0x7fc0e45b4080>
        protocol   = 'https'
        req        = <urllib.request.Request object at 0x7fc1150e7da0>
        response   = <http.client.HTTPResponse object at 0x7fc0e45e8b20>
        self       = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
        timeout    = 60
................../usr/lib/python3.12/urllib/request.py:630: in http_response
    response = self.parent.error(
        code       = 403
        hdrs       = <http.client.HTTPMessage object at 0x7fc0e45b75f0>
        msg        = 'rate limit exceeded'
        request    = <urllib.request.Request object at 0x7fc1150e7da0>
        response   = <http.client.HTTPResponse object at 0x7fc0e45e8b20>
        self       = <urllib.request.HTTPErrorProcessor object at 0x7fc0e45b4080>
................../usr/lib/python3.12/urllib/request.py:559: in error
    return self._call_chain(*args)
        args       = ({'default': [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>], 301: [<urllib.request.HTTPRedirectHa...ult', <urllib.request.Request object at 0x7fc1150e7da0>, <http.client.HTTPResponse object at 0x7fc0e45e8b20>, 403, ...)
        dict       = {'default': [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>], 301: [<urllib.request.HTTPRedirectHan...PRedirectHandler object at 0x7fc0e45b7770>], 303: [<urllib.request.HTTPRedirectHandler object at 0x7fc0e45b7770>], ...}
        http_err   = 1
        meth_name  = 'http_error_403'
        orig_args  = (<urllib.request.Request object at 0x7fc1150e7da0>, <http.client.HTTPResponse object at 0x7fc0e45e8b20>, 403, 'rate limit exceeded', <http.client.HTTPMessage object at 0x7fc0e45b75f0>)
        proto      = 403
        result     = None
        self       = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
................../usr/lib/python3.12/urllib/request.py:492: in _call_chain
    result = func(*args)
        args       = (<urllib.request.Request object at 0x7fc1150e7da0>, <http.client.HTTPResponse object at 0x7fc0e45e8b20>, 403, 'rate limit exceeded', <http.client.HTTPMessage object at 0x7fc0e45b75f0>)
        chain      = {'default': [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>], 301: [<urllib.request.HTTPRedirectHan...PRedirectHandler object at 0x7fc0e45b7770>], 303: [<urllib.request.HTTPRedirectHandler object at 0x7fc0e45b7770>], ...}
        func       = <bound method HTTPDefaultErrorHandler.http_error_default of <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>>
        handler    = <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>
        handlers   = [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>]
        kind       = 'default'
        meth_name  = 'http_error_default'
        self       = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>
req = <urllib.request.Request object at 0x7fc1150e7da0>
fp = <http.client.HTTPResponse object at 0x7fc0e45e8b20>, code = 403
msg = 'rate limit exceeded'
hdrs = <http.client.HTTPMessage object at 0x7fc0e45b75f0>

    def http_error_default(self, req, fp, code, msg, hdrs):
>       raise HTTPError(req.full_url, code, msg, hdrs, fp)
E       urllib.error.HTTPError: HTTP Error 403: rate limit exceeded

code       = 403
fp         = <http.client.HTTPResponse object at 0x7fc0e45e8b20>
hdrs       = <http.client.HTTPMessage object at 0x7fc0e45b75f0>
msg        = 'rate limit exceeded'
req        = <urllib.request.Request object at 0x7fc1150e7da0>
self       = <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>

................../usr/lib/python3.12/urllib/request.py:639: HTTPError

The above exception was the direct cause of the following exception:

    @pytest.fixture(scope="module")
    def relay() -> Iterator[RelayReadyInfo]:
        process = RelayProcess()
        try:
>           yield process.start()

process    = <dimos.web.relay_bridge.relay_process.RelayProcess object at 0x7fc0e45b6690>

.../web/relay_bridge/test_relay_e2e.py:40: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../web/relay_bridge/relay_process.py:73: in start
    deno = ensure_deno()
        self       = <dimos.web.relay_bridge.relay_process.RelayProcess object at 0x7fc0e45b6690>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def ensure_deno() -> str:
        which = shutil.which("deno")
        if which:
            return which
    
        exe_name = "deno.exe" if platform.system() == "Windows" else "deno"
        deno_dir = STATE_DIR / "deno" / DENO_VERSION
        deno_path = deno_dir / exe_name
        if deno_path.exists():
            return str(deno_path)
    
        triple = _deno_triple()
        url = f"https://github..../deno/releases/download/{DENO_VERSION}/deno-{triple}.zip"
        logger.info(f"Downloading deno {DENO_VERSION} from {url}")
        deno_dir.mkdir(parents=True, exist_ok=True)
        try:
            _require_immutable_release()
            with tempfile.TemporaryDirectory(dir=str(deno_dir.parent)) as tmp:
                tmp_path = Path(tmp)
                zip_path = tmp_path / "deno.zip"
                with urllib.request.urlopen(url, timeout=60) as resp, open(zip_path, "wb") as f:
                    shutil.copyfileobj(resp, f)
                with zipfile.ZipFile(zip_path) as z:
                    z.extractall(tmp_path)
                extracted = tmp_path / exe_name
                if not extracted.exists():
                    raise RuntimeError(f"deno binary not found in archive from {url}")
                extracted.chmod(extracted.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
                extracted.replace(deno_path)
        except Exception as e:
>           raise RuntimeError(
                f"deno is required. Auto-download failed: {e}. Install manually from https://deno.com/"
            ) from e
E           RuntimeError: deno is required. Auto-download failed: HTTP Error 403: rate limit exceeded. Install manually from https://deno.com/

deno_dir   = PosixPath('.../dimos/deno/v2.6.10')
deno_path  = PosixPath('.../dimos/deno/v2.6.10/deno')
exe_name   = 'deno'
triple     = 'x86_64-unknown-linux-gnu'
url        = 'https://github..../deno/releases/download/v2.6.10/deno-x86_64-unknown-linux-gnu.zip'
which      = None

dimos/utils/deno.py:65: RuntimeError
dimos.web.relay_bridge.test_relay_e2e::test_reset_stale_discards_partial_frame
Stack Traces | 0.001s run time
def ensure_deno() -> str:
        which = shutil.which("deno")
        if which:
            return which
    
        exe_name = "deno.exe" if platform.system() == "Windows" else "deno"
        deno_dir = STATE_DIR / "deno" / DENO_VERSION
        deno_path = deno_dir / exe_name
        if deno_path.exists():
            return str(deno_path)
    
        triple = _deno_triple()
        url = f"https://github..../deno/releases/download/{DENO_VERSION}/deno-{triple}.zip"
        logger.info(f"Downloading deno {DENO_VERSION} from {url}")
        deno_dir.mkdir(parents=True, exist_ok=True)
        try:
>           _require_immutable_release()

deno_dir   = PosixPath('.../dimos/deno/v2.6.10')
deno_path  = PosixPath('.../dimos/deno/v2.6.10/deno')
exe_name   = 'deno'
triple     = 'x86_64-unknown-linux-gnu'
url        = 'https://github..../deno/releases/download/v2.6.10/deno-x86_64-unknown-linux-gnu.zip'
which      = None

dimos/utils/deno.py:51: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
dimos/utils/deno.py:80: in _require_immutable_release
    with urllib.request.urlopen(url, timeout=60) as resp:
        url        = 'https://api.github..../releases/tags/v2.6.10'
................../usr/lib/python3.12/urllib/request.py:215: in urlopen
    return opener.open(url, data, timeout)
        cadefault  = False
        cafile     = None
        capath     = None
        context    = None
        data       = None
        opener     = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
        timeout    = 60
        url        = 'https://api.github..../releases/tags/v2.6.10'
................../usr/lib/python3.12/urllib/request.py:521: in open
    response = meth(req, response)
        data       = None
        fullurl    = 'https://api.github..../releases/tags/v2.6.10'
        meth       = <bound method HTTPErrorProcessor.http_response of <urllib.request.HTTPErrorProcessor object at 0x7fc0e45b4080>>
        meth_name  = 'https_response'
        processor  = <urllib.request.HTTPErrorProcessor object at 0x7fc0e45b4080>
        protocol   = 'https'
        req        = <urllib.request.Request object at 0x7fc1150e7da0>
        response   = <http.client.HTTPResponse object at 0x7fc0e45e8b20>
        self       = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
        timeout    = 60
................../usr/lib/python3.12/urllib/request.py:630: in http_response
    response = self.parent.error(
        code       = 403
        hdrs       = <http.client.HTTPMessage object at 0x7fc0e45b75f0>
        msg        = 'rate limit exceeded'
        request    = <urllib.request.Request object at 0x7fc1150e7da0>
        response   = <http.client.HTTPResponse object at 0x7fc0e45e8b20>
        self       = <urllib.request.HTTPErrorProcessor object at 0x7fc0e45b4080>
................../usr/lib/python3.12/urllib/request.py:559: in error
    return self._call_chain(*args)
        args       = ({'default': [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>], 301: [<urllib.request.HTTPRedirectHa...ult', <urllib.request.Request object at 0x7fc1150e7da0>, <http.client.HTTPResponse object at 0x7fc0e45e8b20>, 403, ...)
        dict       = {'default': [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>], 301: [<urllib.request.HTTPRedirectHan...PRedirectHandler object at 0x7fc0e45b7770>], 303: [<urllib.request.HTTPRedirectHandler object at 0x7fc0e45b7770>], ...}
        http_err   = 1
        meth_name  = 'http_error_403'
        orig_args  = (<urllib.request.Request object at 0x7fc1150e7da0>, <http.client.HTTPResponse object at 0x7fc0e45e8b20>, 403, 'rate limit exceeded', <http.client.HTTPMessage object at 0x7fc0e45b75f0>)
        proto      = 403
        result     = None
        self       = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
................../usr/lib/python3.12/urllib/request.py:492: in _call_chain
    result = func(*args)
        args       = (<urllib.request.Request object at 0x7fc1150e7da0>, <http.client.HTTPResponse object at 0x7fc0e45e8b20>, 403, 'rate limit exceeded', <http.client.HTTPMessage object at 0x7fc0e45b75f0>)
        chain      = {'default': [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>], 301: [<urllib.request.HTTPRedirectHan...PRedirectHandler object at 0x7fc0e45b7770>], 303: [<urllib.request.HTTPRedirectHandler object at 0x7fc0e45b7770>], ...}
        func       = <bound method HTTPDefaultErrorHandler.http_error_default of <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>>
        handler    = <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>
        handlers   = [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>]
        kind       = 'default'
        meth_name  = 'http_error_default'
        self       = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>
req = <urllib.request.Request object at 0x7fc1150e7da0>
fp = <http.client.HTTPResponse object at 0x7fc0e45e8b20>, code = 403
msg = 'rate limit exceeded'
hdrs = <http.client.HTTPMessage object at 0x7fc0e45b75f0>

    def http_error_default(self, req, fp, code, msg, hdrs):
>       raise HTTPError(req.full_url, code, msg, hdrs, fp)
E       urllib.error.HTTPError: HTTP Error 403: rate limit exceeded

code       = 403
fp         = <http.client.HTTPResponse object at 0x7fc0e45e8b20>
hdrs       = <http.client.HTTPMessage object at 0x7fc0e45b75f0>
msg        = 'rate limit exceeded'
req        = <urllib.request.Request object at 0x7fc1150e7da0>
self       = <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>

................../usr/lib/python3.12/urllib/request.py:639: HTTPError

The above exception was the direct cause of the following exception:

    @pytest.fixture(scope="module")
    def relay() -> Iterator[RelayReadyInfo]:
        process = RelayProcess()
        try:
>           yield process.start()

process    = <dimos.web.relay_bridge.relay_process.RelayProcess object at 0x7fc0e45b6690>

.../web/relay_bridge/test_relay_e2e.py:40: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../web/relay_bridge/relay_process.py:73: in start
    deno = ensure_deno()
        self       = <dimos.web.relay_bridge.relay_process.RelayProcess object at 0x7fc0e45b6690>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def ensure_deno() -> str:
        which = shutil.which("deno")
        if which:
            return which
    
        exe_name = "deno.exe" if platform.system() == "Windows" else "deno"
        deno_dir = STATE_DIR / "deno" / DENO_VERSION
        deno_path = deno_dir / exe_name
        if deno_path.exists():
            return str(deno_path)
    
        triple = _deno_triple()
        url = f"https://github..../deno/releases/download/{DENO_VERSION}/deno-{triple}.zip"
        logger.info(f"Downloading deno {DENO_VERSION} from {url}")
        deno_dir.mkdir(parents=True, exist_ok=True)
        try:
            _require_immutable_release()
            with tempfile.TemporaryDirectory(dir=str(deno_dir.parent)) as tmp:
                tmp_path = Path(tmp)
                zip_path = tmp_path / "deno.zip"
                with urllib.request.urlopen(url, timeout=60) as resp, open(zip_path, "wb") as f:
                    shutil.copyfileobj(resp, f)
                with zipfile.ZipFile(zip_path) as z:
                    z.extractall(tmp_path)
                extracted = tmp_path / exe_name
                if not extracted.exists():
                    raise RuntimeError(f"deno binary not found in archive from {url}")
                extracted.chmod(extracted.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
                extracted.replace(deno_path)
        except Exception as e:
>           raise RuntimeError(
                f"deno is required. Auto-download failed: {e}. Install manually from https://deno.com/"
            ) from e
E           RuntimeError: deno is required. Auto-download failed: HTTP Error 403: rate limit exceeded. Install manually from https://deno.com/

deno_dir   = PosixPath('.../dimos/deno/v2.6.10')
deno_path  = PosixPath('.../dimos/deno/v2.6.10/deno')
exe_name   = 'deno'
triple     = 'x86_64-unknown-linux-gnu'
url        = 'https://github..../deno/releases/download/v2.6.10/deno-x86_64-unknown-linux-gnu.zip'
which      = None

dimos/utils/deno.py:65: RuntimeError
dimos.web.relay_bridge.test_relay_e2e::test_robot_handshake_and_datagram_rtt
Stack Traces | 0.001s run time
def ensure_deno() -> str:
        which = shutil.which("deno")
        if which:
            return which
    
        exe_name = "deno.exe" if platform.system() == "Windows" else "deno"
        deno_dir = STATE_DIR / "deno" / DENO_VERSION
        deno_path = deno_dir / exe_name
        if deno_path.exists():
            return str(deno_path)
    
        triple = _deno_triple()
        url = f"https://github..../deno/releases/download/{DENO_VERSION}/deno-{triple}.zip"
        logger.info(f"Downloading deno {DENO_VERSION} from {url}")
        deno_dir.mkdir(parents=True, exist_ok=True)
        try:
>           _require_immutable_release()

deno_dir   = PosixPath('.../dimos/deno/v2.6.10')
deno_path  = PosixPath('.../dimos/deno/v2.6.10/deno')
exe_name   = 'deno'
triple     = 'x86_64-unknown-linux-gnu'
url        = 'https://github..../deno/releases/download/v2.6.10/deno-x86_64-unknown-linux-gnu.zip'
which      = None

dimos/utils/deno.py:51: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
dimos/utils/deno.py:80: in _require_immutable_release
    with urllib.request.urlopen(url, timeout=60) as resp:
        url        = 'https://api.github..../releases/tags/v2.6.10'
................../usr/lib/python3.12/urllib/request.py:215: in urlopen
    return opener.open(url, data, timeout)
        cadefault  = False
        cafile     = None
        capath     = None
        context    = None
        data       = None
        opener     = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
        timeout    = 60
        url        = 'https://api.github..../releases/tags/v2.6.10'
................../usr/lib/python3.12/urllib/request.py:521: in open
    response = meth(req, response)
        data       = None
        fullurl    = 'https://api.github..../releases/tags/v2.6.10'
        meth       = <bound method HTTPErrorProcessor.http_response of <urllib.request.HTTPErrorProcessor object at 0x7fc0e45b4080>>
        meth_name  = 'https_response'
        processor  = <urllib.request.HTTPErrorProcessor object at 0x7fc0e45b4080>
        protocol   = 'https'
        req        = <urllib.request.Request object at 0x7fc1150e7da0>
        response   = <http.client.HTTPResponse object at 0x7fc0e45e8b20>
        self       = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
        timeout    = 60
................../usr/lib/python3.12/urllib/request.py:630: in http_response
    response = self.parent.error(
        code       = 403
        hdrs       = <http.client.HTTPMessage object at 0x7fc0e45b75f0>
        msg        = 'rate limit exceeded'
        request    = <urllib.request.Request object at 0x7fc1150e7da0>
        response   = <http.client.HTTPResponse object at 0x7fc0e45e8b20>
        self       = <urllib.request.HTTPErrorProcessor object at 0x7fc0e45b4080>
................../usr/lib/python3.12/urllib/request.py:559: in error
    return self._call_chain(*args)
        args       = ({'default': [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>], 301: [<urllib.request.HTTPRedirectHa...ult', <urllib.request.Request object at 0x7fc1150e7da0>, <http.client.HTTPResponse object at 0x7fc0e45e8b20>, 403, ...)
        dict       = {'default': [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>], 301: [<urllib.request.HTTPRedirectHan...PRedirectHandler object at 0x7fc0e45b7770>], 303: [<urllib.request.HTTPRedirectHandler object at 0x7fc0e45b7770>], ...}
        http_err   = 1
        meth_name  = 'http_error_403'
        orig_args  = (<urllib.request.Request object at 0x7fc1150e7da0>, <http.client.HTTPResponse object at 0x7fc0e45e8b20>, 403, 'rate limit exceeded', <http.client.HTTPMessage object at 0x7fc0e45b75f0>)
        proto      = 403
        result     = None
        self       = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
................../usr/lib/python3.12/urllib/request.py:492: in _call_chain
    result = func(*args)
        args       = (<urllib.request.Request object at 0x7fc1150e7da0>, <http.client.HTTPResponse object at 0x7fc0e45e8b20>, 403, 'rate limit exceeded', <http.client.HTTPMessage object at 0x7fc0e45b75f0>)
        chain      = {'default': [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>], 301: [<urllib.request.HTTPRedirectHan...PRedirectHandler object at 0x7fc0e45b7770>], 303: [<urllib.request.HTTPRedirectHandler object at 0x7fc0e45b7770>], ...}
        func       = <bound method HTTPDefaultErrorHandler.http_error_default of <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>>
        handler    = <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>
        handlers   = [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>]
        kind       = 'default'
        meth_name  = 'http_error_default'
        self       = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>
req = <urllib.request.Request object at 0x7fc1150e7da0>
fp = <http.client.HTTPResponse object at 0x7fc0e45e8b20>, code = 403
msg = 'rate limit exceeded'
hdrs = <http.client.HTTPMessage object at 0x7fc0e45b75f0>

    def http_error_default(self, req, fp, code, msg, hdrs):
>       raise HTTPError(req.full_url, code, msg, hdrs, fp)
E       urllib.error.HTTPError: HTTP Error 403: rate limit exceeded

code       = 403
fp         = <http.client.HTTPResponse object at 0x7fc0e45e8b20>
hdrs       = <http.client.HTTPMessage object at 0x7fc0e45b75f0>
msg        = 'rate limit exceeded'
req        = <urllib.request.Request object at 0x7fc1150e7da0>
self       = <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>

................../usr/lib/python3.12/urllib/request.py:639: HTTPError

The above exception was the direct cause of the following exception:

    @pytest.fixture(scope="module")
    def relay() -> Iterator[RelayReadyInfo]:
        process = RelayProcess()
        try:
>           yield process.start()

process    = <dimos.web.relay_bridge.relay_process.RelayProcess object at 0x7fc0e45b6690>

.../web/relay_bridge/test_relay_e2e.py:40: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../web/relay_bridge/relay_process.py:73: in start
    deno = ensure_deno()
        self       = <dimos.web.relay_bridge.relay_process.RelayProcess object at 0x7fc0e45b6690>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def ensure_deno() -> str:
        which = shutil.which("deno")
        if which:
            return which
    
        exe_name = "deno.exe" if platform.system() == "Windows" else "deno"
        deno_dir = STATE_DIR / "deno" / DENO_VERSION
        deno_path = deno_dir / exe_name
        if deno_path.exists():
            return str(deno_path)
    
        triple = _deno_triple()
        url = f"https://github..../deno/releases/download/{DENO_VERSION}/deno-{triple}.zip"
        logger.info(f"Downloading deno {DENO_VERSION} from {url}")
        deno_dir.mkdir(parents=True, exist_ok=True)
        try:
            _require_immutable_release()
            with tempfile.TemporaryDirectory(dir=str(deno_dir.parent)) as tmp:
                tmp_path = Path(tmp)
                zip_path = tmp_path / "deno.zip"
                with urllib.request.urlopen(url, timeout=60) as resp, open(zip_path, "wb") as f:
                    shutil.copyfileobj(resp, f)
                with zipfile.ZipFile(zip_path) as z:
                    z.extractall(tmp_path)
                extracted = tmp_path / exe_name
                if not extracted.exists():
                    raise RuntimeError(f"deno binary not found in archive from {url}")
                extracted.chmod(extracted.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
                extracted.replace(deno_path)
        except Exception as e:
>           raise RuntimeError(
                f"deno is required. Auto-download failed: {e}. Install manually from https://deno.com/"
            ) from e
E           RuntimeError: deno is required. Auto-download failed: HTTP Error 403: rate limit exceeded. Install manually from https://deno.com/

deno_dir   = PosixPath('.../dimos/deno/v2.6.10')
deno_path  = PosixPath('.../dimos/deno/v2.6.10/deno')
exe_name   = 'deno'
triple     = 'x86_64-unknown-linux-gnu'
url        = 'https://github..../deno/releases/download/v2.6.10/deno-x86_64-unknown-linux-gnu.zip'
which      = None

dimos/utils/deno.py:65: RuntimeError
dimos.web.relay_bridge.test_relay_e2e::test_send_frame_paces_with_wait_delivered
Stack Traces | 0.001s run time
def ensure_deno() -> str:
        which = shutil.which("deno")
        if which:
            return which
    
        exe_name = "deno.exe" if platform.system() == "Windows" else "deno"
        deno_dir = STATE_DIR / "deno" / DENO_VERSION
        deno_path = deno_dir / exe_name
        if deno_path.exists():
            return str(deno_path)
    
        triple = _deno_triple()
        url = f"https://github..../deno/releases/download/{DENO_VERSION}/deno-{triple}.zip"
        logger.info(f"Downloading deno {DENO_VERSION} from {url}")
        deno_dir.mkdir(parents=True, exist_ok=True)
        try:
>           _require_immutable_release()

deno_dir   = PosixPath('.../dimos/deno/v2.6.10')
deno_path  = PosixPath('.../dimos/deno/v2.6.10/deno')
exe_name   = 'deno'
triple     = 'x86_64-unknown-linux-gnu'
url        = 'https://github..../deno/releases/download/v2.6.10/deno-x86_64-unknown-linux-gnu.zip'
which      = None

dimos/utils/deno.py:51: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
dimos/utils/deno.py:80: in _require_immutable_release
    with urllib.request.urlopen(url, timeout=60) as resp:
        url        = 'https://api.github..../releases/tags/v2.6.10'
................../usr/lib/python3.12/urllib/request.py:215: in urlopen
    return opener.open(url, data, timeout)
        cadefault  = False
        cafile     = None
        capath     = None
        context    = None
        data       = None
        opener     = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
        timeout    = 60
        url        = 'https://api.github..../releases/tags/v2.6.10'
................../usr/lib/python3.12/urllib/request.py:521: in open
    response = meth(req, response)
        data       = None
        fullurl    = 'https://api.github..../releases/tags/v2.6.10'
        meth       = <bound method HTTPErrorProcessor.http_response of <urllib.request.HTTPErrorProcessor object at 0x7fc0e45b4080>>
        meth_name  = 'https_response'
        processor  = <urllib.request.HTTPErrorProcessor object at 0x7fc0e45b4080>
        protocol   = 'https'
        req        = <urllib.request.Request object at 0x7fc1150e7da0>
        response   = <http.client.HTTPResponse object at 0x7fc0e45e8b20>
        self       = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
        timeout    = 60
................../usr/lib/python3.12/urllib/request.py:630: in http_response
    response = self.parent.error(
        code       = 403
        hdrs       = <http.client.HTTPMessage object at 0x7fc0e45b75f0>
        msg        = 'rate limit exceeded'
        request    = <urllib.request.Request object at 0x7fc1150e7da0>
        response   = <http.client.HTTPResponse object at 0x7fc0e45e8b20>
        self       = <urllib.request.HTTPErrorProcessor object at 0x7fc0e45b4080>
................../usr/lib/python3.12/urllib/request.py:559: in error
    return self._call_chain(*args)
        args       = ({'default': [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>], 301: [<urllib.request.HTTPRedirectHa...ult', <urllib.request.Request object at 0x7fc1150e7da0>, <http.client.HTTPResponse object at 0x7fc0e45e8b20>, 403, ...)
        dict       = {'default': [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>], 301: [<urllib.request.HTTPRedirectHan...PRedirectHandler object at 0x7fc0e45b7770>], 303: [<urllib.request.HTTPRedirectHandler object at 0x7fc0e45b7770>], ...}
        http_err   = 1
        meth_name  = 'http_error_403'
        orig_args  = (<urllib.request.Request object at 0x7fc1150e7da0>, <http.client.HTTPResponse object at 0x7fc0e45e8b20>, 403, 'rate limit exceeded', <http.client.HTTPMessage object at 0x7fc0e45b75f0>)
        proto      = 403
        result     = None
        self       = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
................../usr/lib/python3.12/urllib/request.py:492: in _call_chain
    result = func(*args)
        args       = (<urllib.request.Request object at 0x7fc1150e7da0>, <http.client.HTTPResponse object at 0x7fc0e45e8b20>, 403, 'rate limit exceeded', <http.client.HTTPMessage object at 0x7fc0e45b75f0>)
        chain      = {'default': [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>], 301: [<urllib.request.HTTPRedirectHan...PRedirectHandler object at 0x7fc0e45b7770>], 303: [<urllib.request.HTTPRedirectHandler object at 0x7fc0e45b7770>], ...}
        func       = <bound method HTTPDefaultErrorHandler.http_error_default of <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>>
        handler    = <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>
        handlers   = [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>]
        kind       = 'default'
        meth_name  = 'http_error_default'
        self       = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>
req = <urllib.request.Request object at 0x7fc1150e7da0>
fp = <http.client.HTTPResponse object at 0x7fc0e45e8b20>, code = 403
msg = 'rate limit exceeded'
hdrs = <http.client.HTTPMessage object at 0x7fc0e45b75f0>

    def http_error_default(self, req, fp, code, msg, hdrs):
>       raise HTTPError(req.full_url, code, msg, hdrs, fp)
E       urllib.error.HTTPError: HTTP Error 403: rate limit exceeded

code       = 403
fp         = <http.client.HTTPResponse object at 0x7fc0e45e8b20>
hdrs       = <http.client.HTTPMessage object at 0x7fc0e45b75f0>
msg        = 'rate limit exceeded'
req        = <urllib.request.Request object at 0x7fc1150e7da0>
self       = <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>

................../usr/lib/python3.12/urllib/request.py:639: HTTPError

The above exception was the direct cause of the following exception:

    @pytest.fixture(scope="module")
    def relay() -> Iterator[RelayReadyInfo]:
        process = RelayProcess()
        try:
>           yield process.start()

process    = <dimos.web.relay_bridge.relay_process.RelayProcess object at 0x7fc0e45b6690>

.../web/relay_bridge/test_relay_e2e.py:40: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../web/relay_bridge/relay_process.py:73: in start
    deno = ensure_deno()
        self       = <dimos.web.relay_bridge.relay_process.RelayProcess object at 0x7fc0e45b6690>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def ensure_deno() -> str:
        which = shutil.which("deno")
        if which:
            return which
    
        exe_name = "deno.exe" if platform.system() == "Windows" else "deno"
        deno_dir = STATE_DIR / "deno" / DENO_VERSION
        deno_path = deno_dir / exe_name
        if deno_path.exists():
            return str(deno_path)
    
        triple = _deno_triple()
        url = f"https://github..../deno/releases/download/{DENO_VERSION}/deno-{triple}.zip"
        logger.info(f"Downloading deno {DENO_VERSION} from {url}")
        deno_dir.mkdir(parents=True, exist_ok=True)
        try:
            _require_immutable_release()
            with tempfile.TemporaryDirectory(dir=str(deno_dir.parent)) as tmp:
                tmp_path = Path(tmp)
                zip_path = tmp_path / "deno.zip"
                with urllib.request.urlopen(url, timeout=60) as resp, open(zip_path, "wb") as f:
                    shutil.copyfileobj(resp, f)
                with zipfile.ZipFile(zip_path) as z:
                    z.extractall(tmp_path)
                extracted = tmp_path / exe_name
                if not extracted.exists():
                    raise RuntimeError(f"deno binary not found in archive from {url}")
                extracted.chmod(extracted.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
                extracted.replace(deno_path)
        except Exception as e:
>           raise RuntimeError(
                f"deno is required. Auto-download failed: {e}. Install manually from https://deno.com/"
            ) from e
E           RuntimeError: deno is required. Auto-download failed: HTTP Error 403: rate limit exceeded. Install manually from https://deno.com/

deno_dir   = PosixPath('.../dimos/deno/v2.6.10')
deno_path  = PosixPath('.../dimos/deno/v2.6.10/deno')
exe_name   = 'deno'
triple     = 'x86_64-unknown-linux-gnu'
url        = 'https://github..../deno/releases/download/v2.6.10/deno-x86_64-unknown-linux-gnu.zip'
which      = None

dimos/utils/deno.py:65: RuntimeError
dimos.web.relay_bridge.test_relay_e2e::test_stats_reflect_traffic
Stack Traces | 0.001s run time
def ensure_deno() -> str:
        which = shutil.which("deno")
        if which:
            return which
    
        exe_name = "deno.exe" if platform.system() == "Windows" else "deno"
        deno_dir = STATE_DIR / "deno" / DENO_VERSION
        deno_path = deno_dir / exe_name
        if deno_path.exists():
            return str(deno_path)
    
        triple = _deno_triple()
        url = f"https://github..../deno/releases/download/{DENO_VERSION}/deno-{triple}.zip"
        logger.info(f"Downloading deno {DENO_VERSION} from {url}")
        deno_dir.mkdir(parents=True, exist_ok=True)
        try:
>           _require_immutable_release()

deno_dir   = PosixPath('.../dimos/deno/v2.6.10')
deno_path  = PosixPath('.../dimos/deno/v2.6.10/deno')
exe_name   = 'deno'
triple     = 'x86_64-unknown-linux-gnu'
url        = 'https://github..../deno/releases/download/v2.6.10/deno-x86_64-unknown-linux-gnu.zip'
which      = None

dimos/utils/deno.py:51: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
dimos/utils/deno.py:80: in _require_immutable_release
    with urllib.request.urlopen(url, timeout=60) as resp:
        url        = 'https://api.github..../releases/tags/v2.6.10'
................../usr/lib/python3.12/urllib/request.py:215: in urlopen
    return opener.open(url, data, timeout)
        cadefault  = False
        cafile     = None
        capath     = None
        context    = None
        data       = None
        opener     = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
        timeout    = 60
        url        = 'https://api.github..../releases/tags/v2.6.10'
................../usr/lib/python3.12/urllib/request.py:521: in open
    response = meth(req, response)
        data       = None
        fullurl    = 'https://api.github..../releases/tags/v2.6.10'
        meth       = <bound method HTTPErrorProcessor.http_response of <urllib.request.HTTPErrorProcessor object at 0x7fc0e45b4080>>
        meth_name  = 'https_response'
        processor  = <urllib.request.HTTPErrorProcessor object at 0x7fc0e45b4080>
        protocol   = 'https'
        req        = <urllib.request.Request object at 0x7fc1150e7da0>
        response   = <http.client.HTTPResponse object at 0x7fc0e45e8b20>
        self       = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
        timeout    = 60
................../usr/lib/python3.12/urllib/request.py:630: in http_response
    response = self.parent.error(
        code       = 403
        hdrs       = <http.client.HTTPMessage object at 0x7fc0e45b75f0>
        msg        = 'rate limit exceeded'
        request    = <urllib.request.Request object at 0x7fc1150e7da0>
        response   = <http.client.HTTPResponse object at 0x7fc0e45e8b20>
        self       = <urllib.request.HTTPErrorProcessor object at 0x7fc0e45b4080>
................../usr/lib/python3.12/urllib/request.py:559: in error
    return self._call_chain(*args)
        args       = ({'default': [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>], 301: [<urllib.request.HTTPRedirectHa...ult', <urllib.request.Request object at 0x7fc1150e7da0>, <http.client.HTTPResponse object at 0x7fc0e45e8b20>, 403, ...)
        dict       = {'default': [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>], 301: [<urllib.request.HTTPRedirectHan...PRedirectHandler object at 0x7fc0e45b7770>], 303: [<urllib.request.HTTPRedirectHandler object at 0x7fc0e45b7770>], ...}
        http_err   = 1
        meth_name  = 'http_error_403'
        orig_args  = (<urllib.request.Request object at 0x7fc1150e7da0>, <http.client.HTTPResponse object at 0x7fc0e45e8b20>, 403, 'rate limit exceeded', <http.client.HTTPMessage object at 0x7fc0e45b75f0>)
        proto      = 403
        result     = None
        self       = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
................../usr/lib/python3.12/urllib/request.py:492: in _call_chain
    result = func(*args)
        args       = (<urllib.request.Request object at 0x7fc1150e7da0>, <http.client.HTTPResponse object at 0x7fc0e45e8b20>, 403, 'rate limit exceeded', <http.client.HTTPMessage object at 0x7fc0e45b75f0>)
        chain      = {'default': [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>], 301: [<urllib.request.HTTPRedirectHan...PRedirectHandler object at 0x7fc0e45b7770>], 303: [<urllib.request.HTTPRedirectHandler object at 0x7fc0e45b7770>], ...}
        func       = <bound method HTTPDefaultErrorHandler.http_error_default of <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>>
        handler    = <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>
        handlers   = [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>]
        kind       = 'default'
        meth_name  = 'http_error_default'
        self       = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>
req = <urllib.request.Request object at 0x7fc1150e7da0>
fp = <http.client.HTTPResponse object at 0x7fc0e45e8b20>, code = 403
msg = 'rate limit exceeded'
hdrs = <http.client.HTTPMessage object at 0x7fc0e45b75f0>

    def http_error_default(self, req, fp, code, msg, hdrs):
>       raise HTTPError(req.full_url, code, msg, hdrs, fp)
E       urllib.error.HTTPError: HTTP Error 403: rate limit exceeded

code       = 403
fp         = <http.client.HTTPResponse object at 0x7fc0e45e8b20>
hdrs       = <http.client.HTTPMessage object at 0x7fc0e45b75f0>
msg        = 'rate limit exceeded'
req        = <urllib.request.Request object at 0x7fc1150e7da0>
self       = <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>

................../usr/lib/python3.12/urllib/request.py:639: HTTPError

The above exception was the direct cause of the following exception:

    @pytest.fixture(scope="module")
    def relay() -> Iterator[RelayReadyInfo]:
        process = RelayProcess()
        try:
>           yield process.start()

process    = <dimos.web.relay_bridge.relay_process.RelayProcess object at 0x7fc0e45b6690>

.../web/relay_bridge/test_relay_e2e.py:40: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../web/relay_bridge/relay_process.py:73: in start
    deno = ensure_deno()
        self       = <dimos.web.relay_bridge.relay_process.RelayProcess object at 0x7fc0e45b6690>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def ensure_deno() -> str:
        which = shutil.which("deno")
        if which:
            return which
    
        exe_name = "deno.exe" if platform.system() == "Windows" else "deno"
        deno_dir = STATE_DIR / "deno" / DENO_VERSION
        deno_path = deno_dir / exe_name
        if deno_path.exists():
            return str(deno_path)
    
        triple = _deno_triple()
        url = f"https://github..../deno/releases/download/{DENO_VERSION}/deno-{triple}.zip"
        logger.info(f"Downloading deno {DENO_VERSION} from {url}")
        deno_dir.mkdir(parents=True, exist_ok=True)
        try:
            _require_immutable_release()
            with tempfile.TemporaryDirectory(dir=str(deno_dir.parent)) as tmp:
                tmp_path = Path(tmp)
                zip_path = tmp_path / "deno.zip"
                with urllib.request.urlopen(url, timeout=60) as resp, open(zip_path, "wb") as f:
                    shutil.copyfileobj(resp, f)
                with zipfile.ZipFile(zip_path) as z:
                    z.extractall(tmp_path)
                extracted = tmp_path / exe_name
                if not extracted.exists():
                    raise RuntimeError(f"deno binary not found in archive from {url}")
                extracted.chmod(extracted.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
                extracted.replace(deno_path)
        except Exception as e:
>           raise RuntimeError(
                f"deno is required. Auto-download failed: {e}. Install manually from https://deno.com/"
            ) from e
E           RuntimeError: deno is required. Auto-download failed: HTTP Error 403: rate limit exceeded. Install manually from https://deno.com/

deno_dir   = PosixPath('.../dimos/deno/v2.6.10')
deno_path  = PosixPath('.../dimos/deno/v2.6.10/deno')
exe_name   = 'deno'
triple     = 'x86_64-unknown-linux-gnu'
url        = 'https://github..../deno/releases/download/v2.6.10/deno-x86_64-unknown-linux-gnu.zip'
which      = None

dimos/utils/deno.py:65: RuntimeError
dimos.web.relay_bridge.test_relay_e2e::test_close_signal_stops_writer_and_wakes_waiter
Stack Traces | 0.08s run time
def ensure_deno() -> str:
        which = shutil.which("deno")
        if which:
            return which
    
        exe_name = "deno.exe" if platform.system() == "Windows" else "deno"
        deno_dir = STATE_DIR / "deno" / DENO_VERSION
        deno_path = deno_dir / exe_name
        if deno_path.exists():
            return str(deno_path)
    
        triple = _deno_triple()
        url = f"https://github..../deno/releases/download/{DENO_VERSION}/deno-{triple}.zip"
        logger.info(f"Downloading deno {DENO_VERSION} from {url}")
        deno_dir.mkdir(parents=True, exist_ok=True)
        try:
>           _require_immutable_release()

deno_dir   = PosixPath('.../dimos/deno/v2.6.10')
deno_path  = PosixPath('.../dimos/deno/v2.6.10/deno')
exe_name   = 'deno'
triple     = 'x86_64-unknown-linux-gnu'
url        = 'https://github..../deno/releases/download/v2.6.10/deno-x86_64-unknown-linux-gnu.zip'
which      = None

dimos/utils/deno.py:51: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
dimos/utils/deno.py:80: in _require_immutable_release
    with urllib.request.urlopen(url, timeout=60) as resp:
        url        = 'https://api.github..../releases/tags/v2.6.10'
................../usr/lib/python3.12/urllib/request.py:215: in urlopen
    return opener.open(url, data, timeout)
        cadefault  = False
        cafile     = None
        capath     = None
        context    = None
        data       = None
        opener     = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
        timeout    = 60
        url        = 'https://api.github..../releases/tags/v2.6.10'
................../usr/lib/python3.12/urllib/request.py:521: in open
    response = meth(req, response)
        data       = None
        fullurl    = 'https://api.github..../releases/tags/v2.6.10'
        meth       = <bound method HTTPErrorProcessor.http_response of <urllib.request.HTTPErrorProcessor object at 0x7fc0e45b4080>>
        meth_name  = 'https_response'
        processor  = <urllib.request.HTTPErrorProcessor object at 0x7fc0e45b4080>
        protocol   = 'https'
        req        = <urllib.request.Request object at 0x7fc0e45b6330>
        response   = <http.client.HTTPResponse object at 0x7fc0e9791bd0>
        self       = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
        timeout    = 60
................../usr/lib/python3.12/urllib/request.py:630: in http_response
    response = self.parent.error(
        code       = 403
        hdrs       = <http.client.HTTPMessage object at 0x7fc0e443ae10>
        msg        = 'rate limit exceeded'
        request    = <urllib.request.Request object at 0x7fc0e45b6330>
        response   = <http.client.HTTPResponse object at 0x7fc0e9791bd0>
        self       = <urllib.request.HTTPErrorProcessor object at 0x7fc0e45b4080>
................../usr/lib/python3.12/urllib/request.py:559: in error
    return self._call_chain(*args)
        args       = ({'default': [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>], 301: [<urllib.request.HTTPRedirectHa...ult', <urllib.request.Request object at 0x7fc0e45b6330>, <http.client.HTTPResponse object at 0x7fc0e9791bd0>, 403, ...)
        dict       = {'default': [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>], 301: [<urllib.request.HTTPRedirectHan...PRedirectHandler object at 0x7fc0e45b7770>], 303: [<urllib.request.HTTPRedirectHandler object at 0x7fc0e45b7770>], ...}
        http_err   = 1
        meth_name  = 'http_error_403'
        orig_args  = (<urllib.request.Request object at 0x7fc0e45b6330>, <http.client.HTTPResponse object at 0x7fc0e9791bd0>, 403, 'rate limit exceeded', <http.client.HTTPMessage object at 0x7fc0e443ae10>)
        proto      = 403
        result     = None
        self       = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
................../usr/lib/python3.12/urllib/request.py:492: in _call_chain
    result = func(*args)
        args       = (<urllib.request.Request object at 0x7fc0e45b6330>, <http.client.HTTPResponse object at 0x7fc0e9791bd0>, 403, 'rate limit exceeded', <http.client.HTTPMessage object at 0x7fc0e443ae10>)
        chain      = {'default': [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>], 301: [<urllib.request.HTTPRedirectHan...PRedirectHandler object at 0x7fc0e45b7770>], 303: [<urllib.request.HTTPRedirectHandler object at 0x7fc0e45b7770>], ...}
        func       = <bound method HTTPDefaultErrorHandler.http_error_default of <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>>
        handler    = <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>
        handlers   = [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>]
        kind       = 'default'
        meth_name  = 'http_error_default'
        self       = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>
req = <urllib.request.Request object at 0x7fc0e45b6330>
fp = <http.client.HTTPResponse object at 0x7fc0e9791bd0>, code = 403
msg = 'rate limit exceeded'
hdrs = <http.client.HTTPMessage object at 0x7fc0e443ae10>

    def http_error_default(self, req, fp, code, msg, hdrs):
>       raise HTTPError(req.full_url, code, msg, hdrs, fp)
E       urllib.error.HTTPError: HTTP Error 403: rate limit exceeded

code       = 403
fp         = <http.client.HTTPResponse object at 0x7fc0e9791bd0>
hdrs       = <http.client.HTTPMessage object at 0x7fc0e443ae10>
msg        = 'rate limit exceeded'
req        = <urllib.request.Request object at 0x7fc0e45b6330>
self       = <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>

................../usr/lib/python3.12/urllib/request.py:639: HTTPError

The above exception was the direct cause of the following exception:

    @pytest.fixture
    def own_relay() -> Iterator[RelayProcess]:
        """A started relay process the test may stop itself (stop() is idempotent)."""
        process = RelayProcess()
        try:
>           process.start()

process    = <dimos.web.relay_bridge.relay_process.RelayProcess object at 0x7fc0e9790a40>

.../web/relay_bridge/test_relay_e2e.py:50: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../web/relay_bridge/relay_process.py:73: in start
    deno = ensure_deno()
        self       = <dimos.web.relay_bridge.relay_process.RelayProcess object at 0x7fc0e9790a40>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def ensure_deno() -> str:
        which = shutil.which("deno")
        if which:
            return which
    
        exe_name = "deno.exe" if platform.system() == "Windows" else "deno"
        deno_dir = STATE_DIR / "deno" / DENO_VERSION
        deno_path = deno_dir / exe_name
        if deno_path.exists():
            return str(deno_path)
    
        triple = _deno_triple()
        url = f"https://github..../deno/releases/download/{DENO_VERSION}/deno-{triple}.zip"
        logger.info(f"Downloading deno {DENO_VERSION} from {url}")
        deno_dir.mkdir(parents=True, exist_ok=True)
        try:
            _require_immutable_release()
            with tempfile.TemporaryDirectory(dir=str(deno_dir.parent)) as tmp:
                tmp_path = Path(tmp)
                zip_path = tmp_path / "deno.zip"
                with urllib.request.urlopen(url, timeout=60) as resp, open(zip_path, "wb") as f:
                    shutil.copyfileobj(resp, f)
                with zipfile.ZipFile(zip_path) as z:
                    z.extractall(tmp_path)
                extracted = tmp_path / exe_name
                if not extracted.exists():
                    raise RuntimeError(f"deno binary not found in archive from {url}")
                extracted.chmod(extracted.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
                extracted.replace(deno_path)
        except Exception as e:
>           raise RuntimeError(
                f"deno is required. Auto-download failed: {e}. Install manually from https://deno.com/"
            ) from e
E           RuntimeError: deno is required. Auto-download failed: HTTP Error 403: rate limit exceeded. Install manually from https://deno.com/

deno_dir   = PosixPath('.../dimos/deno/v2.6.10')
deno_path  = PosixPath('.../dimos/deno/v2.6.10/deno')
exe_name   = 'deno'
triple     = 'x86_64-unknown-linux-gnu'
url        = 'https://github..../deno/releases/download/v2.6.10/deno-x86_64-unknown-linux-gnu.zip'
which      = None

dimos/utils/deno.py:65: RuntimeError
dimos.web.relay_bridge.test_relay_e2e::test_info_matches_ready_line
Stack Traces | 0.142s run time
def ensure_deno() -> str:
        which = shutil.which("deno")
        if which:
            return which
    
        exe_name = "deno.exe" if platform.system() == "Windows" else "deno"
        deno_dir = STATE_DIR / "deno" / DENO_VERSION
        deno_path = deno_dir / exe_name
        if deno_path.exists():
            return str(deno_path)
    
        triple = _deno_triple()
        url = f"https://github..../deno/releases/download/{DENO_VERSION}/deno-{triple}.zip"
        logger.info(f"Downloading deno {DENO_VERSION} from {url}")
        deno_dir.mkdir(parents=True, exist_ok=True)
        try:
>           _require_immutable_release()

deno_dir   = PosixPath('.../dimos/deno/v2.6.10')
deno_path  = PosixPath('.../dimos/deno/v2.6.10/deno')
exe_name   = 'deno'
triple     = 'x86_64-unknown-linux-gnu'
url        = 'https://github..../deno/releases/download/v2.6.10/deno-x86_64-unknown-linux-gnu.zip'
which      = None

dimos/utils/deno.py:51: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
dimos/utils/deno.py:80: in _require_immutable_release
    with urllib.request.urlopen(url, timeout=60) as resp:
        url        = 'https://api.github..../releases/tags/v2.6.10'
................../usr/lib/python3.12/urllib/request.py:215: in urlopen
    return opener.open(url, data, timeout)
        cadefault  = False
        cafile     = None
        capath     = None
        context    = None
        data       = None
        opener     = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
        timeout    = 60
        url        = 'https://api.github..../releases/tags/v2.6.10'
................../usr/lib/python3.12/urllib/request.py:521: in open
    response = meth(req, response)
        data       = None
        fullurl    = 'https://api.github..../releases/tags/v2.6.10'
        meth       = <bound method HTTPErrorProcessor.http_response of <urllib.request.HTTPErrorProcessor object at 0x7fc0e45b4080>>
        meth_name  = 'https_response'
        processor  = <urllib.request.HTTPErrorProcessor object at 0x7fc0e45b4080>
        protocol   = 'https'
        req        = <urllib.request.Request object at 0x7fc1150e7da0>
        response   = <http.client.HTTPResponse object at 0x7fc0e45e8b20>
        self       = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
        timeout    = 60
................../usr/lib/python3.12/urllib/request.py:630: in http_response
    response = self.parent.error(
        code       = 403
        hdrs       = <http.client.HTTPMessage object at 0x7fc0e45b75f0>
        msg        = 'rate limit exceeded'
        request    = <urllib.request.Request object at 0x7fc1150e7da0>
        response   = <http.client.HTTPResponse object at 0x7fc0e45e8b20>
        self       = <urllib.request.HTTPErrorProcessor object at 0x7fc0e45b4080>
................../usr/lib/python3.12/urllib/request.py:559: in error
    return self._call_chain(*args)
        args       = ({'default': [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>], 301: [<urllib.request.HTTPRedirectHa...ult', <urllib.request.Request object at 0x7fc1150e7da0>, <http.client.HTTPResponse object at 0x7fc0e45e8b20>, 403, ...)
        dict       = {'default': [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>], 301: [<urllib.request.HTTPRedirectHan...PRedirectHandler object at 0x7fc0e45b7770>], 303: [<urllib.request.HTTPRedirectHandler object at 0x7fc0e45b7770>], ...}
        http_err   = 1
        meth_name  = 'http_error_403'
        orig_args  = (<urllib.request.Request object at 0x7fc1150e7da0>, <http.client.HTTPResponse object at 0x7fc0e45e8b20>, 403, 'rate limit exceeded', <http.client.HTTPMessage object at 0x7fc0e45b75f0>)
        proto      = 403
        result     = None
        self       = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
................../usr/lib/python3.12/urllib/request.py:492: in _call_chain
    result = func(*args)
        args       = (<urllib.request.Request object at 0x7fc1150e7da0>, <http.client.HTTPResponse object at 0x7fc0e45e8b20>, 403, 'rate limit exceeded', <http.client.HTTPMessage object at 0x7fc0e45b75f0>)
        chain      = {'default': [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>], 301: [<urllib.request.HTTPRedirectHan...PRedirectHandler object at 0x7fc0e45b7770>], 303: [<urllib.request.HTTPRedirectHandler object at 0x7fc0e45b7770>], ...}
        func       = <bound method HTTPDefaultErrorHandler.http_error_default of <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>>
        handler    = <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>
        handlers   = [<urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>]
        kind       = 'default'
        meth_name  = 'http_error_default'
        self       = <urllib.request.OpenerDirector object at 0x7fc0e45b5970>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>
req = <urllib.request.Request object at 0x7fc1150e7da0>
fp = <http.client.HTTPResponse object at 0x7fc0e45e8b20>, code = 403
msg = 'rate limit exceeded'
hdrs = <http.client.HTTPMessage object at 0x7fc0e45b75f0>

    def http_error_default(self, req, fp, code, msg, hdrs):
>       raise HTTPError(req.full_url, code, msg, hdrs, fp)
E       urllib.error.HTTPError: HTTP Error 403: rate limit exceeded

code       = 403
fp         = <http.client.HTTPResponse object at 0x7fc0e45e8b20>
hdrs       = <http.client.HTTPMessage object at 0x7fc0e45b75f0>
msg        = 'rate limit exceeded'
req        = <urllib.request.Request object at 0x7fc1150e7da0>
self       = <urllib.request.HTTPDefaultErrorHandler object at 0x7fc0e4400b90>

................../usr/lib/python3.12/urllib/request.py:639: HTTPError

The above exception was the direct cause of the following exception:

    @pytest.fixture(scope="module")
    def relay() -> Iterator[RelayReadyInfo]:
        process = RelayProcess()
        try:
>           yield process.start()

process    = <dimos.web.relay_bridge.relay_process.RelayProcess object at 0x7fc0e45b6690>

.../web/relay_bridge/test_relay_e2e.py:40: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../web/relay_bridge/relay_process.py:73: in start
    deno = ensure_deno()
        self       = <dimos.web.relay_bridge.relay_process.RelayProcess object at 0x7fc0e45b6690>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def ensure_deno() -> str:
        which = shutil.which("deno")
        if which:
            return which
    
        exe_name = "deno.exe" if platform.system() == "Windows" else "deno"
        deno_dir = STATE_DIR / "deno" / DENO_VERSION
        deno_path = deno_dir / exe_name
        if deno_path.exists():
            return str(deno_path)
    
        triple = _deno_triple()
        url = f"https://github..../deno/releases/download/{DENO_VERSION}/deno-{triple}.zip"
        logger.info(f"Downloading deno {DENO_VERSION} from {url}")
        deno_dir.mkdir(parents=True, exist_ok=True)
        try:
            _require_immutable_release()
            with tempfile.TemporaryDirectory(dir=str(deno_dir.parent)) as tmp:
                tmp_path = Path(tmp)
                zip_path = tmp_path / "deno.zip"
                with urllib.request.urlopen(url, timeout=60) as resp, open(zip_path, "wb") as f:
                    shutil.copyfileobj(resp, f)
                with zipfile.ZipFile(zip_path) as z:
                    z.extractall(tmp_path)
                extracted = tmp_path / exe_name
                if not extracted.exists():
                    raise RuntimeError(f"deno binary not found in archive from {url}")
                extracted.chmod(extracted.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
                extracted.replace(deno_path)
        except Exception as e:
>           raise RuntimeError(
                f"deno is required. Auto-download failed: {e}. Install manually from https://deno.com/"
            ) from e
E           RuntimeError: deno is required. Auto-download failed: HTTP Error 403: rate limit exceeded. Install manually from https://deno.com/

deno_dir   = PosixPath('.../dimos/deno/v2.6.10')
deno_path  = PosixPath('.../dimos/deno/v2.6.10/deno')
exe_name   = 'deno'
triple     = 'x86_64-unknown-linux-gnu'
url        = 'https://github..../deno/releases/download/v2.6.10/deno-x86_64-unknown-linux-gnu.zip'
which      = None

dimos/utils/deno.py:65: RuntimeError

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

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.

install.sh: smoke test never times out, and interrupting it reports "installation failed"

1 participant