Skip to content

[WIP] Android: backend compat (CI only, do not merge) - #205

Draft
nt-nerdtechnic wants to merge 11 commits into
mainfrom
feat/android-backend-compat
Draft

nt-nerdtechnic wants to merge 11 commits into
mainfrom
feat/android-backend-compat

Conversation

@nt-nerdtechnic

Copy link
Copy Markdown
Collaborator

CI-only draft for the Android branch, kept up to date with main after each release. Do not merge.

This push merges origin/main (d1d81ef, v0.2.21 + #204) into feat/android-backend-compat:

  • cli_vendors/codex.py: kept the Android loopback-proxy spawn env next to main's extensions inventory.
  • tests/test_codex_home.py: kept the no-os.link auth promotion test next to main's new hook-trust, skills-link and temp-file tests.
  • .gitleaksignore: added the commit-less fingerprint for main's quick-menu token alphabet (a known false positive), which the merge's staged scan sees as new.

🤖 Generated with Claude Code

nt-nerdtechnic and others added 11 commits October 4, 2026 18:20
Android's CPython has no os.link, and skills_store read it at import time
while computing _DIRECTORY_FDS, so the whole backend failed to start there.
Detect hard-link support instead and, without it, publish each file with an
O_EXCL copy that still refuses to replace an entry that raced us; rollback
compares against the copy's own inode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
promote_stranded_auth hard-linked the newest in-pane auth.json into the
real Codex home. Where os.link does not exist (Android) it raised
AttributeError past the OSError handler. Fall back to an exclusive copy
that keeps the source mode, so a concurrent writer still wins.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
plugins/host.py imports packaging.version at load, but packaging only
arrived through the dev group (pytest, pyinstaller). An install with runtime
dependencies alone could not import the backend.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Chaquopy's CPython on Android has neither os.openpty nor os.forkpty, so
every terminal spawn failed there. Unlock /dev/ptmx and open the slave by
number (TIOCSPTLCK, TIOCGPTN) instead. Wherever os.openpty exists, which is
every desktop build, spawn still calls pty.openpty unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Android has only /system/bin/sh, and Termux keeps its shells under its own
prefix, so shell_command and the login-path probe pointed at files that are
not there. Use /bin/sh and /bin/bash when they exist, as before, and fall
back to PATH and then /system/bin/sh.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…proxy

Codex ships as a static musl binary, and musl resolves names from
/etc/resolv.conf alone, which Android does not have, so every request
failed. On Android only (osplat.paths.needs_loopback_dns_proxy), the backend
starts a CONNECT proxy bound to 127.0.0.1 and a Codex spawn gets
HTTPS_PROXY pointing at it, SSL_CERT_FILE from certifi and TMPDIR. The proxy
resolves through the system, never reads the tunnelled bytes, and records
the hosts asked for so the UI can disclose them (ANDROID_DISCLOSED_HOSTS).

VendorSpec.spawn_env_extra is the per-spawn counterpart of
spawn_env_defaults with the same precedence: a value from the request or
the inherited environment always wins. It returns {} on every desktop
platform, so their spawns are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Any app on an Android device can connect to 127.0.0.1, so the CONNECT proxy
was an open relay: another app could reach the network through it, around
its own permissions. Mint a random token per process and answer 407 to a
CONNECT without Proxy-Authorization: Basic navide:<token>. Codex receives
the credential in HTTPS_PROXY's userinfo and sends it itself; other apps
cannot read another process's environment on Android.

Destinations stay unrestricted and are disclosed instead (hosts_seen).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keeps both sides of the two conflicts: the Android loopback-proxy spawn env
in cli_vendors/codex.py next to main's extensions inventory, and the
no-os.link auth promotion test next to main's hook-trust, skills-link and
temp-file tests in test_codex_home.py.

.gitleaksignore gains the commit-less fingerprint of main's quick-menu
token alphabet: the merge's staged scan sees that file as new, and main's
entry is pinned to the commit that added it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The Android branch resolves sh when /bin/sh is missing, so on the Windows
runner _posix_paths.shell_command found Git's sh.exe and the desktop
assertion failed. Production Windows never calls the POSIX module; the
test now states the host it describes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Both tests delete os.link to stand in for Android, but on a Windows host
the fallback then reaches os.fchmod, which Windows CPython lacks. The
fallback only runs where os.link is missing, which is never Windows, and
it asserts POSIX modes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

This branch has not been deployed

No deployments
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.

1 participant