bludesign/ai-development-environment-upload-dsyms is a GitHub Action that uploads dSYMs from CI. It finds the .dSYM bundles in an archive or build folder, zips them, and sends them through the resumable upload in requests of at most 16 MiB. Uploads pass Cloudflare’s 100 MB request limit, resume after a dropped connection, and outlast Cloudflare’s 125-second timeout while the control plane indexes them. Crashes waiting on the uploaded UUIDs are symbolicated again automatically.
Requirements
- An API key. dSYM uploads need an
X-API-Key. - A GitHub-hosted or self-hosted runner that runs
node24actions, which needs runner version 2.328.0 or newer. macOS and Linux runners work, and Xcode is not required. - A control plane the runner can reach. Behind Cloudflare Access, the dSYM routes are either bypassed or the runner has a service token.
Add the step
1
Create an API key
Create a key on the API Keys page.
2
Add a secret and a variable
In the repository’s Settings → Secrets and variables → Actions, add the key as the
AIDE_API_KEY secret and the control plane’s origin, such as https://aide.example.com, as the AIDE_URL variable.3
Upload after the build
Add the step after the job archives or builds the app.
Inputs
A line of
dsym_paths that starts with ! leaves out what the other lines match, not the bundles found inside a matched folder. To leave some bundles out, match the bundles themselves:
Outputs
How uploads work
- Finding. A matched
.dSYMis a bundle. Any other matched folder is searched for.dSYMbundles, without following links to folders. A matched.zipis uploaded unchanged. - Zipping. The bundles go into
dSYMs.zipwith only what the control plane keeps:Contents/Info.plistand the files inContents/Resources/DWARF. Links are stored as the files they point to, because a zip with links is refused. Timestamps and permissions are fixed, so uploading the same dSYMs again makes the same zip, which the control plane stores once and reports as a duplicate. More than 500 bundles or 19 GiB are split acrossdSYMs-2.zipand so on. - Sending. Each zip goes through
POST /api/dsyms/uploads,PATCHchunks withUpload-Offset, andPOST /api/dsyms/uploads/{id}/complete, with the zip’s SHA-256 declared up front and checked at the end. A failed request is retried with exponential backoff, honoringRetry-After. After a failure the action asks for the server’s offset withHEAD, so a chunk that arrived without its answer is not sent twice, and an upload continues across a control plane restart. - Completing. Indexing a large zip can take longer than a proxy waits. After a
504, a524, or a timeout, the action asks again, which waits for the indexing in progress and returns its result, for up to an hour. If the control plane restarted while indexing, the step fails and can be run again. - Failures. When one zip fails, the others are still uploaded and the step fails at the end. A failed or cancelled step deletes its unfinished upload, so it does not wait out the 24-hour cleanup in Uploads in progress.
Cloudflare
Cloudflare Access
- Bypass the dSYM routes
- Keep them behind Access
Hosting lists
/api/dsyms and /api/dsyms/uploads/* among the paths that bypass Access, because the control plane checks the API key itself. With the bypass in place, the action needs no extra headers.401, and the step fails with a hint about the service-token headers.
Limits and timeouts
Bot protection
Bot Fight Mode, Super Bot Fight Mode, and WAF rules that issue challenges can answer CI with a challenge it cannot pass. The action reports the challenge instead of retrying it. Super Bot Fight Mode and challenge rules can be skipped for/api/dsyms* with a WAF custom rule’s Skip action. Bot Fight Mode on the Free plan cannot be skipped, so it has to be off for the zone.
Other proxies and networks
Troubleshooting
The API key is invalid or inactive
The API key is invalid or inactive
The control plane refused
X-API-Key. Check the AIDE_API_KEY secret against the API Keys page; a disabled, expired, or revoked key is refused.Cloudflare Access stopped the request
Cloudflare Access stopped the request
Access redirected the request to its sign-in page or answered
401. Send the service token’s headers in headers, and check that the Access application has a Service Auth policy that includes the token. Alternatively, bypass Access for /api/dsyms and /api/dsyms/uploads/*.Cloudflare answered with a challenge or blocked the request
Cloudflare answered with a challenge or blocked the request
A challenge comes from bot protection or a WAF rule; skip the rule for
/api/dsyms*, or turn off Bot Fight Mode. A block without a challenge comes from a WAF rule, IP access rule, or Access policy; search the Ray ID in the log in Cloudflare’s security events.A proxy refused the request body as too large
A proxy refused the request body as too large
Raise the proxy’s request body limit to at least 16 MiB, or lower
chunk_size.No dSYMs matched dsym_paths
No dSYMs matched dsym_paths
Check the paths against the build output, and build with
DEBUG_INFORMATION_FORMAT=dwarf-with-dsym. A bundle without files in Contents/Resources/DWARF is skipped with a warning. For builds that produce no dSYMs, set if_no_files_found to warn or ignore.The server indexed fewer dSYMs than it received
The server indexed fewer dSYMs than it received
The control plane skips a DWARF file it cannot read as Mach-O. The warning names the bundles it skipped.
The server stopped indexing or could not index
The server stopped indexing or could not index
If the control plane restarted while indexing, run the step again. Otherwise, Uploads in progress on the dSYMs page shows why the upload failed.
Related pages
dSYMs
The dSYMs table, dashboard uploads, and the upload API.
Crashes
How crash reports are symbolicated with dSYMs.
API Keys
Create the key the action uploads with.
Hosting and networking
Cloudflare Access paths and reverse proxies.