# Evidence capture: desktop Android SDK environment

PR: [#11648](https://github.com/pingdotgg/t3code/pull/11648), [#11648](https://git.cafe/pingdotgg/t3code/pulls/11648).

Before: [843f4e3529](https://github.com/pingdotgg/t3code/commit/843f4e35292391897089535e8f1af59b322e70f8). After: [e90cc01e4b](https://github.com/pingdotgg/t3code/commit/e90cc01e4b95283528d5f65dedaa19a7114a3cd8).

```text
BEFORE_SHA=843f4e35292391897089535e8f1af59b322e70f8
AFTER_SHA=e90cc01e4b95283528d5f65dedaa19a7114a3cd8
BRANCH=takeover/pr-11648
WORKTREE=/home/ubuntu/wt/pr-11648
MACHINE=fs-01 OS=Linux-x86_64 ELECTRON=44.9.3 HARNESS=Codex MODEL=gpt-6.1-sol
T3CODE_DEV_SEED=0
```

The original [fork](https://github.com/thelonewander3r/t3code/tree/fix/desktop-android-sdk-env) identity and [c4c9ff1](https://github.com/thelonewander3r/t3code/commit/c4c9ff1ee99dd41d53ade45bf47b8295d449de27) were supplied in the brief/matrix; they were not independently fetched. The preserved matrix names an earlier after revision; these captures use the HEAD above.

## Outcome

PASS: all 44 final real Linux Electron launches match the backend tuples and local Android discovery expected by the matrix. A1–A11 and C1–C11 each have before/after captures. C3/C4 retain inherited ROOT-only j on both revisions; discovery invokes j, with no profile HOME shadowing. All 88 final PNGs were opened; each run has two captures 500 ms apart with identical pixels and identical file hashes. All 44 interaction recordings pass ffprobe frame checks and full ffmpeg decode.

Synthetic SDK fixtures implement discovery commands only, contain all three required tool paths, and intentionally have no AVD. SDK-available UI says “The Android SDK is installed, but no virtual device exists.” No emulator boot or real Android SDK execution is claimed.

## Matrix results

Tuples are (ANDROID_HOME, ANDROID_SDK_ROOT) from the directly spawned backend exec environment. U = absent, E = empty, W = whitespace. h/r/i/j and `home sdk`/`root sdk` are distinct roots below `/tmp/ev-11648/sdks`, outside SDK defaults and PATH. Exact bytes are in each backend.json and results.json.

| Row | Before tuple | After tuple | Before / after selected root | Result |
|---|---|---|---|---|
| A1 | (U, U) | (h, r) | none / h | PASS |
| A2 | (U, U) | (h, U) | none / h | PASS |
| A3 | (U, U) | (U, r) | none / r | PASS |
| A4 | (E, E) | (h, r) | none / h | PASS |
| A5 | (W, W) | (h, r) | none / h | PASS |
| A6 | (E, U) | (E, r) | none / r | PASS |
| A7 | (U, E) | (h, E) | none / h | PASS |
| A8 | (U, U) | (U, r) | none / r | PASS |
| A9 | (U, U) | (h, U) | none / h | PASS |
| A10 | (U, U) | (home sdk, root sdk) | none / home sdk | PASS |
| A11 | (U, U) | (h, r) | none / h | PASS |
| C1 | (i, j) | (i, j) | i / i | PASS |
| C2 | (i, U) | (i, U) | i / i | PASS |
| C3 | (U, j) | (U, j) | j / j | PASS |
| C4 | (U, j) | (U, j) | j / j | PASS |
| C5 | (i, U) | (i, U) | i / i | PASS |
| C6 | (U, U) | (U, U) | none / none | PASS |
| C7 | (E, j) | (E, j) | j / j | PASS |
| C8 | (i, E) | (i, E) | i / i | PASS |
| C9 | (U, U) | (U, U) | none / none | PASS |
| C10 | (E, W) | (E, W) | none / none | PASS |
| C11 | (padded i, U) | (padded i, U) | i / i | PASS |

## Attribution and isolation

Every app was launched by env -i with an explicit small allowlist, not through a login shell. Each final fixture starts with a fresh throwaway HOME and T3CODE_HOME. T3CODE_DEV_SEED=0, telemetry and auto-update are disabled; no live T3 data or credentials were used. The allowlist includes only HOME, SHELL, ordinary Node/npm/system PATH, T3 isolation/config keys, LANG, DISPLAY, XAUTHORITY, XDG_RUNTIME_DIR and this headless session’s D-Bus address, plus the fixture’s inherited H/R keys.

The actual profile probe records /usr/bin/bash with argv `/bin/bash -ilc <marker command>`, its Electron parent PID and throwaway HOME in all 44 shell-probe.json files. Each fixture’s profile and profile-loaded.log are retained. A11 preserves stdout banner and stderr noise in separate files; neither leaks into backend H/R values.

The backend is a direct child of the Electron PID captured at spawn. H/R were read from its NUL-delimited /proc/PID/environ immediately after exec, before any terminal profile can mask hydration. Final process trees still contain that same backend PID. The UI drive uses the owned Electron renderer over CDP, Settings → Integrations → Device hub → Refresh. Local discovery sentinels record selected-root label, exact argv, PID, parent PID and filtered H/R. No remote or EAS hosts are configured.

Backend and device-hub environments are intentionally distinct: LocalDeviceHost normalizes its hub child’s HOME SDK key to the selected root. Only backend.json establishes the original tuple; sentinel logs establish downstream selection.

## Build and source proof

Both are real desktop builds run by installed Electron under headless-desktop/Xvfb. HEAD desktop/web/backend were built normally. Before was bundled with the exact merge-base DesktopShellEnvironment module supplied by a temporary source-load plugin; all other production sources are unchanged between these commits. The checkout’s tracked module was never swapped. Source-map contents match the exact revision modules, with bundle hashes in artifact-provenance.json. The ignored desktop output in the checkout was restored from the preserved HEAD artifact.

W1/W2 and S1–S6: source evidence in source-controls.md, paired source snapshots and source-index.json. Windows profile fields/install path and Darwin-only fallback are unchanged. Linux execution is not labeled a Windows/macOS runtime pass. The focused existing DesktopShellEnvironment suite passes 14/14 tests; focused-tests.log preserves command output.

No tracked file changes, commits, pushes, PR edits/posts, or PR watches. Normal ignored build output and ordinary package-manager cache entries were produced. All evidence scripts, fixtures, staged artifacts and reports are under /tmp/ev-11648. Captured app/recording/headless processes are stopped; cleanup.json reports no remaining recorded live PIDs.

## Auditor index

- results.json: machine-readable 44-row tuples, roots, labels, PIDs and pass results.
- affected-transcript.md and controls-transcript.md: grouped launch, probe, backend, discovery, UI and stable-pair transcripts.
- runs/<row>-<revision>/: launch.json, backend.json, process-tree.json, shell-probe.json, fixture-profile.sh (in bundle; locally home/.bash_profile), profile-loaded.log, ui.txt, SDK sentinel JSONL, paired stills, stability.json and interaction.mp4.
- artifact-provenance.json, production.diff, source-controls.md, source-index.json, source/: revision and unchanged-platform evidence.
- image-review.json and image-hash-groups.json: every opened final PNG; repeated screen bytes are expected because the visible status has only two outcomes. Each is independently recaptured in its own launch and paired with that launch’s recording and PID evidence.
- media-validation.json: ffprobe frame inventory and full ffmpeg decode results.
- diagnostics/: rejected setup attempt logs and native installer failure. The 48-byte partial initial MP4 and superseded early captures are excluded from publication.
- files.json in the archive: every published bundle file, byte length and SHA-256.

Unverified: actual emulator launch/AVD behavior (out of scope; synthetic discovery fixtures), and Windows/macOS runtime execution (source controls per matrix). No Linux matrix rows remain unverified.

## Friction

FRICTION infra | GUI allowlist initially had no Node because /usr/bin/node is absent | Include the installed ordinary Node/npm bin directory explicitly, while excluding SDK tools from PATH.

FRICTION infra | expo-device-hub installation failed in node-datachannel's prebuild-install fallback; fallback `_prebuild` had no usable N-API argument | Install isolated pinned hub dependencies, fetch the matching official v0.32.3 napi-v8 Linux x64 binary, verify import, and copy only this tool cache into each fresh fixture. See repair-hub.sh and hub repair logs.

FRICTION tool | `vp pack --config` was interpreted as an entry instead of a config option and rebuilt ignored output incorrectly | Use Vite+ Pack's programmatic `build({...packOptions,config:false})` with an explicit source-load plugin; verify revision source in sourcemaps. Restore the checkout's ignored desktop output from the preserved HEAD build.

FRICTION tool | The first ffmpeg child did not survive its launching command and left a 48-byte partial MP4 | Reject that recording; keep the batch-owned recorder in a captured separate session, close its stdin with `q`, and decode every accepted final recording before upload.

FRICTION tool | agent-browser was absent from PATH | Load its Electron/core guidance through npm's isolated package cache, inspect initial UI with its CDP CLI, then use the repository's playwright-core to replay only the observed role-locator flow in the owned Electron renderer. No separate web browser substitutes for Electron.

FRICTION brief | Early launches proved profiles were read but did not yet retain the complete -ilc argv | Refresh those fixtures with a profile observer that reads the actual shell's /proc executable, argv and parent PID.

Public links are appended after upload. The bundle excludes runtime databases, credential stores, browser profiles, downloaded dependency trees and executable build artifacts; it includes scripts, evidence logs, captures, recordings and source proof.

## Public uploads (verbatim ghdrop output)

[source-controls.md](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/fb69d3acf6a69c9f/source-controls.md)

[pr-11648-evidence.tar.gz](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/84597f12be7e546c/pr-11648-evidence.tar.gz)
[affected-transcript.md](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/86a07adafaed9e88/affected-transcript.md)
[controls-transcript.md](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/06446f7e1fd930aa/controls-transcript.md)
[results.json](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/fb57bffaef239b11/results.json)
[media-validation.json](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/ebba0770dfe06e0a/media-validation.json)
[artifact-provenance.json](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/05da0c2e88f0af19/artifact-provenance.json)
[friction.md](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/a4899ba0c991694d/friction.md)
[archive-sha256.txt](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/3f6f4a673453e690/archive-sha256.txt)
![A1-before.png](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/166c2599aa4c4d7a/A1-before.png)
![A1-after.png](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/766f326e625028f8/A1-after.png)
![C3-before.png](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/f3b08f704be79c90/C3-before.png)
![C3-after.png](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/1a4647985c7aabaa/C3-after.png)
![C4-before.png](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/1e2bc377f4a1a3bb/C4-before.png)
![C4-after.png](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/7fed0889d5284efb/C4-after.png)

All 15 artifact URLs above returned HTTP 200 to independent HEAD requests. See links-verification.json. The source/control report and all matrix evidence are available; no artifact links remain unverified.
