gh-api.mjs: a GitHub client for shells where gh cannot do TLS
Measured 2026-08-20/21 across a full parent/subagent × sandboxed/flag-off matrix. All four cells fail identically for gh; node succeeds in all of them.
The mechanism, corrected
Claude Code's srt egress proxy is a plain CONNECT tunnel. There is no interception of the TLS stream, the upstream certificates are genuine, and there is nothing to install.
gh fails because OSStatus -26276 is a Security-framework internal error: the certificate verifier cannot consult the trust store at all, because the sandbox denies Mach IPC to securityd, and Go on darwin has no other certificate-verification path.
node works because it ships its own CA bundle and never needs to talk to securityd.
The launch-time proxy rule, proven empirically
Node only honors HTTPS_PROXY when NODE_USE_ENV_PROXY=1 is present at launch: undici wires its global dispatcher during bootstrap.
| Variant | Command shape | Result |
|---|---|---|
| In-script assignment | process.env.NODE_USE_ENV_PROXY='1' then fetch() | getaddrinfo ENOTFOUND (went direct, bypassed the tunnel) |
| No variable at all | node probe.mjs | getaddrinfo ENOTFOUND |
| Launch-time variable | NODE_USE_ENV_PROXY=1 node probe.mjs | HTTP 200 |
The helper therefore re-execs itself once with NODE_USE_ENV_PROXY=1 in the child environment when the variable is absent. That is the proven launch-time variant, packaged so callers never have to remember it.
Usage
$ node scripts/gh-api.mjs GET /rate_limit
{"resources":{"core":{"limit":5000,"used":91,"remaining":4909,...
$ node scripts/gh-api.mjs GET /repos/yonatangross/orchestkit
{"id":...,"name":"orchestkit","full_name":"yonatangross/orchestkit",...
$ node scripts/gh-api.mjs POST /repos/OWNER/REPO/pulls body.json
# body.json is sent verbatim as the JSON request body
$ echo 'query { viewer { login } }' | node scripts/gh-api.mjs graphql
{"data":{"viewer":{"login":"yonatangross"}}}
$ node scripts/gh-api.mjs GET /repos/yonatangross/no-such-repo-xyz
GhApiError: HTTP 404 for GET https://api.github.com/repos/yonatangross/no-such-repo-xyz: {"message":"Not Found",...}
$ echo $?
1
Named error, status code, first 200 characters of the body. No stack trace.
What the sandbox allowlist entry does, and does not do
Does: adding api.github.com to sandbox.network.allowedDomains in the project's .claude/settings.json lets sandboxed curl and node reach the GitHub API through the tunnel without a per-command override.
Does not: it does not fix gh itself. The securityd Mach IPC denial is upstream of any hostname allowlist; gh fails the TLS handshake before the destination matters.
OrchestKit playground · branch feat/gh-api-helper · 2026-08-21. Token handling: the helper reads gh auth token via execFileSync and never prints it.