← Notes
June 29, 2026·4 min readopensourceengineeringexpo

Contributing to Expo: One Fix Proposed, Two PRs I Chose Not to Open. The Discipline Is in the No.

Contributing to Expo: One Fix Proposed, Two PRs I Chose Not to Open. The Discipline Is in the No.

The fastest way to get your open-source pull request ignored is to open one that should not exist. A speculative fix for a bug that lives in someone else's framework. A "fix" for something already fixed in the next release. A change you cannot actually verify. Maintainers learn to distrust contributors who fire PRs at every issue, and they are right to.

So when I pointed an autonomous contributor agent at three open Expo issues, the rule was simple: judge mergeability first, and only write code if the issue is a real, reproducible, scoped bug with no existing fix. Three issues went in. One fix came out. Here is each, including the two I walked away from, because the walking away is the part worth writing about.

The one worth fixing: eas-cli swallows # in env values

eas-cli#2813: eas env:pull writes a .env file, and a value containing a # gets silently truncated when you read it back.

The root cause is a one-line assumption. The command built each line as ${name}=${value}. But dotenv treats an unquoted # as the start of an inline comment, so a value like a hex color or a URL with a fragment loses everything from the # onward:

# what eas-cli wrote
COLOR=#ffffff
# what dotenv reads back
COLOR=            (everything after # is a comment)

The fix is small and mechanical: a serializeDotenvValue helper that quotes only when needed, prefers single quotes (literal in dotenv, and it dodges dotenv-expand's $ interpolation), and falls back to double quotes with \n/\r escaping. The important part was not writing the helper, it was proving it actually round-trips. I checked the escaping against the exact bundled dotenv@16.3.1 parser, whose double-quote handling only un-escapes \n and \r, so the output reads back as written.

Evidence before opening anything:

yarn test src/commands/env/__tests__/EnvPull.test.ts   -> 16 passed (3 new)
yarn test src/commands/env/__tests__/                  -> 57 passed
yarn typecheck                                         -> clean
oxlint + oxfmt                                         -> 0 warnings, formatted

This one earned a pull request: a draft, with the repro, the root cause, the fix, and the test output. One honest caveat went in the body too: a value containing both a single and a double quote cannot be represented by that dotenv version's parser. That is a parser limitation, not something the encoding can fix, and saying so up front is part of the job.

The two I did not open

expo#46977: expo-contacts presentAccessPicker shows a blank sheet. Works on the iOS Simulator, blank on real devices. It looked like an Expo bug until I read the triage. The lone comment linked two Apple Developer Forum threads where Apple's own DTS engineers confirmed it as an iOS framework bug, with Feedback IDs, reproducible using Apple's own ContactsUI sample code. Expo's native wrapper is a thin, correct SwiftUI bridge; the blank sheet originates inside Apple's ContactsUI after the modifier fires. There is no Expo-side line to change. It also only reproduces on physical hardware, so even a guess could not be verified. Three stop conditions at once: upstream, unverifiable, not scoped-fixable. No PR.

config-plugins#299: the Branch plugin breaks the iOS build on Expo 53. Real bug: SDK 53 moved the prebuilt AppDelegate to Swift, and RCTBridge was not exposed to it through the bridging header, so the build fails with cannot find type 'RCTBridge' in scope. But diffing the plugin source across tags showed the fix already shipped: a later commit added a withDangerousMod that injects #import <React/RCTBridge.h> into the bridging header, and it is present in every release from v11.0.0 onward. The repo also keeps a strict one-version-per-SDK mapping, so there was nowhere to land an "SDK 53" fix without breaking that convention. The correct resolution is the one already in the thread: move to a newer plugin version. Nothing to add. No PR.

The scoreboard

issuelooked likeverdictoutput
eas-cli#2813env file bugreal, scoped, testabledraft PR with tests + evidence
expo#46977Expo bugconfirmed Apple framework bugno PR (upstream)
config-plugins#299plugin bugalready fixed in a later releaseno PR (already resolved)

One in three became a pull request. That is not a low hit rate, it is the point. An hour spent confirming a bug is upstream, or already fixed, is an hour that saves a maintainer from triaging noise, and saves you from attaching your name to a PR that gets closed. Reproduce before you fix. Verify before you open. And when the honest answer is "this should not be a PR," that is a finding worth shipping too, it just ships as a comment, or as nothing at all.

Building an AI agent?

I'm packaging how I ship them into one kit. Early access:

AI Agent Starter Kit →