Everframe turns raw crash frames into file, function and line using private build artifacts: web and Hermes source maps, Apple dSYMs, Android R8 mappings and unstripped Android native libraries. Each runtime gets its build identity and uploads its artifacts from tooling that runs inside your normal build:
| Runtime | How identity is set | How it uploads |
|---|---|---|
| Web (Vite, webpack, Rollup, esbuild, Next on webpack) | @everframe/bundler-plugin stamps a fresh build ID into the bundle | The same plugin, at the end of the build |
| Expo | @everframe/metro stamps the bundle identity | The @everframe/expo plugin uploads from native release builds |
| Bare React Native | @everframe/metro stamps the bundle identity | npx @everframe/cli setup react-native adds the same upload to Gradle and Xcode once |
Expo OTA (eas update) | @everframe/metro | npx @everframe/cli upload-expo-export |
| iOS / tvOS native (dSYMs) | The image UUIDs the linker writes into every binary | The Xcode build phase from npx @everframe/cli setup xcode, or dsym upload-build --archive after an archive |
| Android JVM with R8 | The dev.everframe Gradle plugin packages a content-derived mapping ID with the app | The same plugin, after assembleRelease/bundleRelease |
| Android native libraries | The GNU build ID in every .so | The same plugin, or npx @everframe/cli elf upload-build --binaries-dir |
| Vega OS (preview) | The Vega CLI names each bundle by its SHA-256 | npx @everframe/cli sourcemaps upload-vega after build-vega; see Vega OS |
The identity embedded in the shipped app must exactly match the identity of the uploaded artifacts. Everframe does not guess from an app version, filename, or the newest upload. Existing reports without an identity cannot be tagged later.
These artifacts map frames; they cannot run your app. To reproduce reports on a live device, upload a runnable build as well. It is a separate CLI step with the same token; see Try on device.
Use the right credentials
Your app uses a public SDK ingest key. It is expected to ship in browser or app code and can submit reports only:
const PUBLIC_SDK_KEY = 'evf_live_PUBLIC_EXAMPLE';
Artifact upload uses a different credential: an API token with explicit
artifacts:write scope and access to the project that owns the target app.
Keep it in trusted CI as the masked EVERFRAME_API_TOKEN environment variable.
Never put this token in an app bundle, a NEXT_PUBLIC_* variable, source code,
or a CLI argument. The app UUID (appId, or EVERFRAME_APP_ID for the CLI) is
the target app, not an SDK key. It is the App ID on the app’s Setup tab
in the dashboard, and the UUID in the app’s dashboard URL. The app itself
takes the evf_live_… SDK key, never this UUID: as sdkKey on web, React and
React Native, and as EverframeConfig(sdkKey:) on iOS.
Everything below uploads only when the token is present, so set it for release
builds in CI and not on developer machines.
export EVERFRAME_API_TOKEN='<masked artifacts:write token>' # CI secret
export EVERFRAME_API_URL='https://everframe.dev/api/v1' # optional; this is the default
Symbol uploads never fail your build by default. When a build integration
(the Xcode phase, the React Native and Expo build steps, dsym upload-build --xcode/--archive/--app, elf upload-build --binaries-dir) has no
EVERFRAME_API_TOKEN, or an upload fails, it prints a warning: line (an Xcode
or Gradle build warning, or a line in the EAS log) and the build continues, in
CI and locally. Crashes from that build then show raw frames until its
artifacts are uploaded, and the dashboard names what is missing. To gate
releases on symbols, set EVERFRAME_SYMBOLS_STRICT=1 (or pass --strict):
a missing token or a failed upload then fails the build. Manual commands with
explicit inputs, such as sourcemaps upload or dsym upload-build --binary,
always exit nonzero on failure. Every request has a timeout, and build
integrations stop uploading after EVERFRAME_UPLOAD_TIMEOUT_SECONDS (600 by
default) with a warning; files uploaded before then are kept.
The commands on this page run the CLI with npx @everframe/cli. The Gradle
plugin and the Xcode phase run EVERFRAME_CLI_JS when it points at a built
dist/index.js, then the copy in your project’s node_modules, and otherwise
npx with the CLI version they pin.
Repeating an upload with the same identity and identical artifacts resumes
safely. Reusing an identity for different bytes returns 409 build_conflict;
assign a new identity and rebuild the app.
Web
Install the bundler plugin and add it to your build. No build ID, URL prefix or SDK configuration is needed:
npm i -D @everframe/bundler-plugin
// vite.config.ts
import everframe from '@everframe/bundler-plugin/vite';
export default {
plugins: [everframe({ appId: process.env.EVERFRAME_APP_ID })],
};
The other bundlers use the same options from their own subpath:
import everframe from '@everframe/bundler-plugin/webpack'; // plugins: [everframe({ appId })]
import everframe from '@everframe/bundler-plugin/rollup'; // plugins: [everframe({ appId })]
import everframe from '@everframe/bundler-plugin/esbuild'; // plugins: [everframe({ appId })]
For Next.js, wrap the config. Only client production webpack builds are
stamped and uploaded, so build releases with next build --webpack. Next 16
builds with Turbopack by default; those builds still succeed but are not stamped
or uploaded, and the plugin prints a warning saying so:
// next.config.ts
import type { NextConfig } from 'next';
import { withEverframe } from '@everframe/bundler-plugin/next';
const nextConfig: NextConfig = {};
export default withEverframe(nextConfig, { appId: process.env.EVERFRAME_APP_ID });
next build --webpack
Options are appId, buildId, deleteAfterUpload and apiUrl. When appId
is missing or not an app UUID, a local build prints a warning and the plugin
does nothing; in CI (CI=true) the build fails. Set deleteAfterUpload: false
when another tool reads the same maps after the build, and remove them yourself
before deploying. In each build the plugin:
- generates a fresh build ID (or uses
EVERFRAME_BUILD_IDwhen set) and stamps it into the bundle;@everframe/weband@everframe/reactpick it up automatically, and an explicitappBuildstill takes precedence; - turns on hidden source maps when you have not configured them;
- uploads the maps as path-only artifacts (
~/assets/app.js), so matching does not depend on the deployed origin or CDN. Packaged TV apps that load scripts fromfile://are supported; - deletes the maps after the server confirms the upload, so they never reach your deploy directory;
- fails the build when
CIistrueor1andEVERFRAME_API_TOKENis missing, and only warns and skips the upload on a local build.
The Vite plugin skips non-client (SSR) builds and the dev server. The esbuild
plugin runs only when minify: true or NODE_ENV=production.
Turbopack or other bundlers (manual)
Turbopack is not supported by the plugin. Generate a unique build ID before compiling and pass it to both the SDK and the upload job. Build IDs are exact, nonblank strings of at most 200 UTF-16 code units:
import { EverframeProvider } from '@everframe/react';
<EverframeProvider config={{
sdkKey: PUBLIC_SDK_KEY,
appVersion: '2.4.0',
appBuild: WEB_BUILD_ID,
}}>
<App />
</EverframeProvider>
For @everframe/web, set the same appBuild in init(). Then upload the exact
JavaScript and adjacent .js.map files produced by that build:
npx @everframe/cli sourcemaps upload \
--app-id "$EVERFRAME_APP_ID" \
--build "$WEB_BUILD_ID" \
--dir dist/assets \
--url-prefix https://app.example.com/assets/ \
--delete-after-upload
--url-prefix is optional. Without it, artifacts upload as path-only ~/…
URLs and match a frame by the longest path suffix, whatever the origin. When you
do pass it, it must be the exact deployed origin and path represented by --dir,
including any CDN origin or base path, and must end in /. The CLI pairs
adjacent JavaScript and map files, reports JavaScript without a map, and
rejects orphan maps or an empty upload.
--delete-after-upload removes maps only after the server confirms the build
is ready and the CLI rechecks their hashes. It never deletes JavaScript. Do not
rebuild after upload: deploy those same JavaScript bytes, and ensure no source
maps remain in any public deploy directory or CDN payload.
Expo
Each Hermes build, Android and iOS, gets its own identity automatically.
-
Install the SDK and the build tooling:
npm i @everframe/react-native npm i -D @everframe/metro @everframe/expo @everframe/cli -
Wrap the Metro config:
// metro.config.js const { getDefaultConfig } = require('expo/metro-config'); const { withEverframe } = require('@everframe/metro'); module.exports = withEverframe(getDefaultConfig(__dirname)); -
Add the config plugin with your app UUID:
{ "expo": { "plugins": [ [ "@everframe/expo", { "appId": "00000000-0000-4000-8000-000000000000" } ] ] } } -
Set the CI secret
EVERFRAME_API_TOKEN. Native release builds, local or on EAS Build, now upload their Hermes bytecode and composed map by themselves. iOS release builds also upload the app’s dSYMs (see Apple dSYMs), and Android release builds apply thedev.everframeGradle plugin, which uploads R8 mappings and native libraries (see Android R8).
expo prebuild adds the upload step to Gradle and Xcode and defines the iOS
SOURCEMAP_FILE build setting for you; running it again does not duplicate
anything or overwrite a SOURCEMAP_FILE you set yourself. The step finds the
CLI through Node inside Gradle and Xcode. iOS Debug builds skip it. Without
EVERFRAME_API_TOKEN, or when an upload fails, it prints a warning and the
build continues, on EAS Build (which sets CI=1) as well as locally; set
EVERFRAME_SYMBOLS_STRICT=1 to fail the build instead. On Android the step
reads the bundle of the variant being built, so flavored builds such as
tvRelease upload their own map. A Kotlin-script build.gradle.kts is not
supported. The SDK reads the identity Metro injects, so no manual SDK
configuration of the bundle is needed.
If your project copies its own Android or iOS template files over the generated
ones after prebuild, those copies replace the upload step. Add it to the
templates once instead, with npx @everframe/cli setup react-native --app-id <uuid> --project <dir>, where <dir> contains the template android/ and ios/
folders.
The upload step is a few lines: in android/app/build.gradle,
apply plugin: "dev.everframe" and everframe { appId = "<uuid>" }, with the
Gradle plugin running the Hermes upload itself; in Xcode, a short build phase
that runs everframe xcode build-phase. Upgrading @everframe/cli and the
Gradle plugin updates the upload logic without editing your project. Both
honour NODE_BINARY.
OTA updates. After eas update has written the export, upload its maps:
npx @everframe/cli upload-expo-export
By default it reads dist and stages into .everframe. Use --dist <dir> and
--staging <dir> to change them. The app UUID comes from --app-id <uuid>, the
EVERFRAME_APP_ID environment variable, or the @everframe/expo entry in a
static app.json. Keep the export’s maps out of the OTA payload and any public
artifact.
Bare React Native
-
Install the SDK and tooling:
npm i @everframe/react-native npm i -D @everframe/metro @everframe/cli -
Wrap
metro.config.js:const { withEverframe } = require('@everframe/metro'); module.exports = withEverframe(config); -
Add the upload to the native projects once, then commit the diff:
npx @everframe/cli setup react-native --app-id 00000000-0000-4000-8000-000000000000Use
--project <dir>when the app is not in the current directory. The command editsandroid/app/build.gradleand the Xcode project, defines the iOSSOURCEMAP_FILEbuild setting, and is safe to run again. -
Set the CI secret
EVERFRAME_API_TOKEN.
The generated steps behave as in Expo: iOS release builds upload dSYMs after
the Hermes map, android/build.gradle gains the dev.everframe Gradle plugin
(R8 mappings and native libraries upload after release builds), iOS Debug builds skip, and a missing token or failed upload
warns without failing the build unless EVERFRAME_SYMBOLS_STRICT=1 is set.
In both setups the step runs before CocoaPods’ [CP] Embed Pods Frameworks
phase. With use_frameworks!, the step follows the app’s run-path links to
the pod frameworks in the build products and uploads their dSYMs before they
are embedded. Static pods are part of the app binary and are covered.
Prebuilt pod frameworks that ship without dSYMs, such as React Native’s
React.framework and hermesvm.framework, print warning: no dSYM for … and
their frames stay raw.
Give the final Hermes bytecode and the final composed Metro/Hermes map to the uploader; a Metro-only intermediate map cannot translate Hermes bytecode offsets. The generated steps do this for release builds. To upload by hand, for example from a custom pipeline:
npx @everframe/cli sourcemaps upload-hermes \
--app-id "$EVERFRAME_APP_ID" \
--build "$HERMES_BUILD_ID" \
--platform android \
--bundle-name index.android.bundle \
--bundle android/app/build/generated/assets/react/release/index.android.bundle \
--source-map android/app/build/generated/sourcemaps/react/release/index.android.bundle.map
The Hermes uploader preserves both input files and rejects
--delete-after-upload; exclude the map from the app, OTA package, and public
artifacts yourself. A JS-only update cannot add missing native bridge support;
rebuild the host app with matching JS and native SDK components.
Android R8
Apply the dev.everframe Gradle plugin to the application module once:
// app/build.gradle.kts
plugins {
id("com.android.application")
id("dev.everframe") version "1.2.1"
}
everframe {
appId.set("00000000-0000-4000-8000-000000000000") // or set EVERFRAME_APP_ID
}
With EVERFRAME_API_TOKEN set in CI, every minified release variant uploads
its final mapping.txt after assembleRelease or bundleRelease
(uploadEverframeReleaseR8Mapping, or uploadEverframeTvReleaseR8Mapping for
a tv flavor). The mapping ID is r8- plus the SHA-256 of mapping.txt; the
plugin packages it in the APK or bundle, and Everframe.start reports it, so
there is nothing to add to EverframeConfig. Rebuilding identical sources
gives the same ID, so retries are safe. Without a token, or when an upload
fails, the build prints a warning and continues (EVERFRAME_SYMBOLS_STRICT=1
fails it instead). Other build types upload when listed in
everframe { buildTypes }.
To upload by hand, pass the same ID the app reports:
npx @everframe/cli r8 upload \
--app-id "$EVERFRAME_APP_ID" \
--mapping-id "<the app's r8MappingId>" \
--mapping app/build/outputs/mapping/release/mapping.txt
EverframeConfig.r8MappingId overrides the packaged ID; it must match
[A-Za-z0-9][A-Za-z0-9._-]{0,127}. R8 retraces Java and Kotlin JVM frames.
Frames in native libraries use Android native libraries.
Android native libraries
Native crash frames are symbolicated from the unstripped .so files your build
keeps. Everframe matches them by GNU build ID and ABI, never by file name. The
dev.everframe Gradle plugin uploads them after every release build
(uploadEverframeReleaseNativeSymbols): it compares the libraries the APK ships
with the unstripped copies from merge<Variant>NativeLibs. A variant without
native code does nothing.
The plugin only looks at the ABIs the variant packages. Prebuilt libraries from
AARs, such as libhermes.so, libc++_shared.so or AndroidX’s, usually ship
without debug information. They are expected, are listed only at --info, and
their frames stay raw. Your own libraries without symbols produce one warning
with their count; run the build with --info to list them.
Without the plugin, run the CLI after assembleRelease or bundleRelease:
npx @everframe/cli elf upload-build --binaries-dir \
app/build/intermediates/stripped_native_libs/release/stripReleaseDebugSymbols/out/lib \
--symbols-dir app/build/intermediates/merged_native_libs/release/mergeReleaseNativeLibs/out/lib
Pass --strict to require symbols for every library; a missing one then fails
with missing_matching_elf, naming its ABI, build ID and path. Use the explicit
--binary <file> form to check a fixed list.
Apple dSYMs
Native iOS and tvOS crash frames are symbolicated from the dSYMs of the exact build, matched by image UUID and architecture. Install the Xcode build phase once, and every Release build and archive uploads its dSYMs:
npx @everframe/cli setup xcode --project App.xcodeproj --app-id 00000000-0000-4000-8000-000000000000
The command adds an “Upload Everframe Symbols” Run Script phase as the last
phase of every application target (--target <name> narrows it) and is safe to
run again. It sets ENABLE_USER_SCRIPT_SANDBOXING = NO on those targets,
because the phase reads embedded frameworks, dSYM folders and node_modules.
The phase needs Node; it uses the project’s @everframe/cli when installed,
and npx otherwise.
--app-id is the App ID (a UUID) of the Everframe app the target reports to,
not the evf_live_… SDK key the app passes to EverframeConfig. The
installer writes it into the phase. Without --app-id, the phase reads
EVERFRAME_APP_ID from the build environment, which includes every build
setting of the target.
What the phase uploads:
- Required: the app executable and every app extension in
PlugIns/orExtensions/, including a tvOS Top Shelf extension. A required binary without its dSYM is reported asmissing_matching_dsymafter the other symbols upload; with--strictit fails the build instead. - Optional: embedded frameworks and dylibs. Prebuilt vendor frameworks often
ship without dSYMs; each prints
warning: no dSYM for …and the other symbols still upload. Pass--strict(or setEVERFRAME_SYMBOLS_STRICT=1) to require them. - Skipped: Debug builds (set
EVERFRAME_UPLOAD_DEBUG=1to upload them), builds withoutDEBUG_INFORMATION_FORMAT = dwarf-with-dsym, Swift runtime libraries and watchOS content.
iOS and tvOS devices and simulators (arm64, arm64e, x86_64) are supported. The
phase declares the dSYM as a build input only for dwarf-with-dsym builds, which
orders it after dSYM generation; Debug (dwarf) builds declare Info.plist
instead, so the phase never fails a build over a missing dSYM. The
EVERFRAME_DSYM_INPUT build settings that setup xcode writes do this.
XcodeGen regenerates the project, so add the phase to project.yml instead:
npx @everframe/cli setup xcode --print-script > scripts/upload-everframe-symbols.sh
targets:
App:
settings:
base:
ENABLE_USER_SCRIPT_SANDBOXING: NO
EVERFRAME_DSYM_INPUT: $(EVERFRAME_DSYM_INPUT_$(DEBUG_INFORMATION_FORMAT:c99extidentifier))
EVERFRAME_DSYM_INPUT_dwarf_with_dsym: $(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)/Contents/Resources/DWARF/$(EXECUTABLE_NAME)
EVERFRAME_DSYM_INPUT_dwarf: $(TARGET_BUILD_DIR)/$(INFOPLIST_PATH)
EVERFRAME_DSYM_INPUT_: $(TARGET_BUILD_DIR)/$(INFOPLIST_PATH)
postBuildScripts:
- name: Upload Everframe Symbols
path: scripts/upload-everframe-symbols.sh
shell: /bin/bash
basedOnDependencyAnalysis: false
inputFiles:
- $(EVERFRAME_DSYM_INPUT)
- $(TARGET_BUILD_DIR)/$(INFOPLIST_PATH)
An iPhone and an Apple TV target that report to two Everframe apps need
one App ID each (and one SDK key each, in each target’s
EverframeConfig). Run the installer once per target; each run changes
only the target it names:
npx @everframe/cli setup xcode --project App.xcodeproj --target App --app-id "$PHONE_APP_ID"
npx @everframe/cli setup xcode --project App.xcodeproj --target AppTV --app-id "$TV_APP_ID"
With XcodeGen, print the script once without --app-id, give both targets the
settings and postBuildScripts entry above, and set EVERFRAME_APP_ID per
target:
targets:
App:
settings:
base:
EVERFRAME_APP_ID: 00000000-0000-4000-8000-000000000001 # the phone app's App ID
AppTV:
settings:
base:
EVERFRAME_APP_ID: 00000000-0000-4000-8000-000000000002 # the TV app's App ID
If both targets report to one Everframe app, they share one App ID and one SDK key.
fastlane, Xcode Cloud and other CI can upload an archive instead:
build_app(scheme: "App")
sh("npx", "--yes", "@everframe/cli", "dsym", "upload-build", "--archive", lane_context[SharedValues::XCODEBUILD_ARCHIVE])
# ci_scripts/ci_post_xcodebuild.sh (install Node in ci_post_clone.sh)
if [ -n "${CI_ARCHIVE_PATH:-}" ]; then npx --yes @everframe/cli dsym upload-build --archive "$CI_ARCHIVE_PATH"; fi
--app-id defaults to EVERFRAME_APP_ID; an archive upload takes the App ID
of the app in that archive. React Native and Expo apps get the same dSYM upload
from their generated build step.
Enable processing on the API
Artifact upload and frame processing are separate. Apply the API migrations,
then deploy the entire API dist output together, including
dist/processor-child.js and its shared chunks. Set this on the API instance
and restart it:
SOURCE_MAP_WORKER_ENABLED=true
The worker is disabled by default. For R8, provision Java 17 or newer and the
pinned R8 9.4.17 runtime, then also set R8_WORKER_ENABLED=true; that flag does
not start the shared worker by itself. Web and Hermes processing need only the
shared flag. Apple dSYMs also need NATIVE_SYMBOL_WORKER_ENABLED=true, and
Android native libraries (.so files) need ELF_SYMBOL_WORKER_ENABLED=true.
Both native formats use the same pinned LLVM tools: NATIVE_SYMBOLIZER_PATH and
NATIVE_DWARFDUMP_PATH, each with its _SHA256.
The per-project artifact quota defaults to 10 GiB; set
ARTIFACT_PROJECT_QUOTA_BYTES on the API to change it.
Reports can arrive before their artifacts or while processing is disabled. Once the exact upload is ready, the worker can enrich retained reports without changing the raw envelope, report ID, client fingerprint, occurrence count, or webhook history.
One late-upload recovery path remains unfinished: a completed processor-v1 result is not reopened solely because maps for retained generic cause frames arrive later. Do not treat the current branch as end-to-end completion of generic cause mapping or its optimized dashboard presentation.
Read and troubleshoot results
The error detail stack opens in Mapped mode when mapped web, Hermes, Apple native or Android native frames are available, or Retraced mode for R8 output. Choose Raw at any time to see the frames captured by the SDK. A partial result can mix mapped and raw frames; Everframe never substitutes a nearby build’s artifact.
A mapped JavaScript frame is named after the function it is in. Metro’s maps
(Expo web, React Native and Vega OS) record each function’s range, so those
names are exact, and anonymous functions get Metro’s descriptive names such as
setTimeout$argument_0. For other bundlers the name comes from the frame
below it on the stack, the call site, when the frame’s own source declares that
name; otherwise the frame keeps the name the browser reported, which may be
minified.
For Web and React, uploaded maps also group errors by function across builds and support release-aware regression handling; see Error triage & releases.
| Result | What to check |
|---|---|
missing_build or missing_js_bundle | Use the bundler plugin or @everframe/metro (or set the web appBuild) so the build ships its identity. Untagged reports cannot infer it later. |
missing_artifacts | Confirm the token’s project access, target app UUID, exact build or mapping ID, upload completion, and retention. |
Web missing_artifact or no_mapping | Confirm the throw-site map was emitted and uploaded for that exact build. If you passed --url-prefix, compare the full captured frame URL with it, including CDN origin and base path; path-only ~/… artifacts match by path suffix. |
Hermes no_mapping | Confirm the uploaded bytecode and map are the final composed pair from the shipped build. |
409 build_conflict or 410 build_expired | An expired or removed build accepts the identical files again. Changed bytes need a fresh ID and a rebuild. |
| Pending indefinitely | Check migrations, SOURCE_MAP_WORKER_ENABLED=true, the deployed dist/processor-child.js, private blob availability, and worker logs. |
| R8 stays raw | Also check R8_WORKER_ENABLED=true and the server’s Java/R8 runtime. |
| Apple or Android native frames stay raw | Also check NATIVE_SYMBOL_WORKER_ENABLED=true (dSYMs) or ELF_SYMBOL_WORKER_ENABLED=true (Android .so), and the pinned LLVM tool paths and hashes. |
401 or 403 | Use a non-revoked API token with artifacts:write and project access, never the public SDK key. |
503 processing_limit | Let the CLI’s bounded retry honor Retry-After, or retry after processor capacity is available. |
invalid_map or another failed result | Check artifact format and limits. Raw frames remain available. |
| dSYM missing | The note under the stack names each image, architecture and UUID without a dSYM. Check the build log for missing_matching_dsym or warning: no dSYM lines, DEBUG_INFORMATION_FORMAT = dwarf-with-dsym for Release, and ENABLE_USER_SCRIPT_SANDBOXING = NO (xcode_script_sandboxed). Apple system libraries have no uploadable dSYMs. |
| ELF symbols missing | The note names each library, ABI and GNU build ID. Prebuilt stripped libraries cannot be symbolicated; for your own libraries, keep debug information in the unstripped .so and upload it. |
413 project_quota_exceeded | The project’s storage is full even after removing older builds. Builds uploaded in the last 24 hours and builds used for symbolication in the last 7 days are kept. Retry later or ask for a larger quota. |
Current limits are 32 MiB per JavaScript source map, 512 MiB per R8 mapping,
512 MiB per dSYM file, 64 MiB per native library, 256 MiB per web build, 500
artifacts per web build, and 10 GiB of stored or reserved artifacts per
project. The CLI compresses R8 mappings with gzip for the upload; the quota
counts their uncompressed size. When an upload would exceed the project
quota, Everframe removes older builds, only as many as the upload needs:
first builds that already expired, then builds never used for symbolication
(oldest first), then the least recently used. Builds never used for
symbolication are kept for 24 hours after their upload (or an identical
re-upload), and builds used in the last 7 days are kept. Events already mapped
keep their mapped frames. Ready artifacts expire after 90 days; incomplete
uploads expire after 24 hours. An expired or removed build can be uploaded
again with identical files: new and waiting occurrences are then symbolicated,
and Resolve in release returns with the next occurrence. Different files under
an expired build ID are refused with 410 build_expired.