Skip to content
Everframe Docs
Documentation

Symbols & source maps

Tag the exact build, upload its private mapping artifacts from trusted CI, and keep Raw frames available beside the mapped result.

Updated

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:

RuntimeHow identity is setHow it uploads
Web (Vite, webpack, Rollup, esbuild, Next on webpack)@everframe/bundler-plugin stamps a fresh build ID into the bundleThe same plugin, at the end of the build
Expo@everframe/metro stamps the bundle identityThe @everframe/expo plugin uploads from native release builds
Bare React Native@everframe/metro stamps the bundle identitynpx @everframe/cli setup react-native adds the same upload to Gradle and Xcode once
Expo OTA (eas update)@everframe/metronpx @everframe/cli upload-expo-export
iOS / tvOS native (dSYMs)The image UUIDs the linker writes into every binaryThe Xcode build phase from npx @everframe/cli setup xcode, or dsym upload-build --archive after an archive
Android JVM with R8The dev.everframe Gradle plugin packages a content-derived mapping ID with the appThe same plugin, after assembleRelease/bundleRelease
Android native librariesThe GNU build ID in every .soThe same plugin, or npx @everframe/cli elf upload-build --binaries-dir
Vega OS (preview)The Vega CLI names each bundle by its SHA-256npx @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_ID when set) and stamps it into the bundle; @everframe/web and @everframe/react pick it up automatically, and an explicit appBuild still 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 from file:// are supported;
  • deletes the maps after the server confirms the upload, so they never reach your deploy directory;
  • fails the build when CI is true or 1 and EVERFRAME_API_TOKEN is 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.

  1. Install the SDK and the build tooling:

    npm i @everframe/react-native
    npm i -D @everframe/metro @everframe/expo @everframe/cli
  2. Wrap the Metro config:

    // metro.config.js
    const { getDefaultConfig } = require('expo/metro-config');
    const { withEverframe } = require('@everframe/metro');
    
    module.exports = withEverframe(getDefaultConfig(__dirname));
  3. Add the config plugin with your app UUID:

    {
      "expo": {
        "plugins": [
          [
            "@everframe/expo",
            {
              "appId": "00000000-0000-4000-8000-000000000000"
            }
          ]
        ]
      }
    }
  4. 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 the dev.everframe Gradle 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

  1. Install the SDK and tooling:

    npm i @everframe/react-native
    npm i -D @everframe/metro @everframe/cli
  2. Wrap metro.config.js:

    const { withEverframe } = require('@everframe/metro');
    
    module.exports = withEverframe(config);
  3. Add the upload to the native projects once, then commit the diff:

    npx @everframe/cli setup react-native --app-id 00000000-0000-4000-8000-000000000000

    Use --project <dir> when the app is not in the current directory. The command edits android/app/build.gradle and the Xcode project, defines the iOS SOURCEMAP_FILE build setting, and is safe to run again.

  4. 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/ or Extensions/, including a tvOS Top Shelf extension. A required binary without its dSYM is reported as missing_matching_dsym after the other symbols upload; with --strict it 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 set EVERFRAME_SYMBOLS_STRICT=1) to require them.
  • Skipped: Debug builds (set EVERFRAME_UPLOAD_DEBUG=1 to upload them), builds without DEBUG_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.

ResultWhat to check
missing_build or missing_js_bundleUse the bundler plugin or @everframe/metro (or set the web appBuild) so the build ships its identity. Untagged reports cannot infer it later.
missing_artifactsConfirm the token’s project access, target app UUID, exact build or mapping ID, upload completion, and retention.
Web missing_artifact or no_mappingConfirm 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_mappingConfirm the uploaded bytecode and map are the final composed pair from the shipped build.
409 build_conflict or 410 build_expiredAn expired or removed build accepts the identical files again. Changed bytes need a fresh ID and a rebuild.
Pending indefinitelyCheck migrations, SOURCE_MAP_WORKER_ENABLED=true, the deployed dist/processor-child.js, private blob availability, and worker logs.
R8 stays rawAlso check R8_WORKER_ENABLED=true and the server’s Java/R8 runtime.
Apple or Android native frames stay rawAlso 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 403Use a non-revoked API token with artifacts:write and project access, never the public SDK key.
503 processing_limitLet the CLI’s bounded retry honor Retry-After, or retry after processor capacity is available.
invalid_map or another failed resultCheck artifact format and limits. Raw frames remain available.
dSYM missingThe 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 missingThe 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_exceededThe 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.