Skip to main content
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 node24 actions, 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.
The dSYMs are recorded with the repository as their Project, the run ID as their Build ID, and the workflow run as their Link. The job summary lists each dSYM with its UUIDs and a link to its details page.

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 .dSYM is a bundle. Any other matched folder is searched for .dSYM bundles, without following links to folders. A matched .zip is uploaded unchanged.
  • Zipping. The bundles go into dSYMs.zip with only what the control plane keeps: Contents/Info.plist and the files in Contents/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 across dSYMs-2.zip and so on.
  • Sending. Each zip goes through POST /api/dsyms/uploads, PATCH chunks with Upload-Offset, and POST /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, honoring Retry-After. After a failure the action asks for the server’s offset with HEAD, 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, a 524, 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.
Store the API key and service-token values as secrets, never in the workflow file. The action masks them in the log and never follows a redirect, so the key is only sent to url. Revoke a key on the API Keys page if it leaks.

Cloudflare

Cloudflare 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.
Without valid headers, Access answers with a redirect to its sign-in page or with a 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 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.
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/*.
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.
Raise the proxy’s request body limit to at least 16 MiB, or lower chunk_size.
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 control plane skips a DWARF file it cannot read as Mach-O. The warning names the bundles it skipped.
If the control plane restarted while indexing, run the step again. Otherwise, Uploads in progress on the dSYMs page shows why the upload failed.

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.