@newinstance/bugwatch-cli
v0.1.1
Published
BugWatch CLI — upload Apple dSYM debug symbols for native crash symbolication.
Readme
@newinstance/bugwatch-cli
Upload Apple .dSYM debug symbols to BugWatch so native iOS and macOS crashes are
symbolicated — function names, file paths, and line numbers instead of raw memory
addresses. One command, CI-friendly, runs anywhere Node 18+ is available.
npx @newinstance/bugwatch-cli symbols upload MyApp.xcarchive \
--release 1.4.2 --build-number 318Requirements
- Node.js 18 or later (ESM-native package)
- A macOS machine or macOS CI runner — dSYM files are produced by Xcode
- A BugWatch API key with the
symbols:uploadpermission scope (see Getting an API key)
Install
No install needed — run it on demand with npx (requires Node 18+):
npx @newinstance/bugwatch-cli symbols upload <path> ...Or install it globally — the bin name is bugwatch, so this exposes bugwatch on $PATH:
npm install -g @newinstance/bugwatch-cli
bugwatch symbols upload <path> ...Build from source (contributors / unreleased revisions)
To run a local build (e.g. an unreleased change, or an air-gapped runner without npm access):
# from the repo root
cd bugwatch-cli
npm install # or: bun install
npm run build # outputs dist/ + bin/bugwatch.mjs
node bin/bugwatch.mjs symbols upload <path> ...Getting an API key
- Open the merchant dashboard for your organisation.
- Navigate to API Keys (project-level or org-level depending on your plan).
- Create a secret key (
sk_test_…/sk_live_…). - Make sure the key has the
symbols:uploadpermission scope assigned. The key validator rejects requests from keys that lack this scope. - Copy the full value — it is shown only once. Store it as a CI secret (never hard-code it or commit it to source control).
The key is sent on every request as the x-api-key: <keyId>:<secret> header.
It is never placed in the URL or logged; the CLI prints a redacted form
(sk_l…3456) for human confirmation only.
Authentication
| Method | How |
|---|---|
| CLI flag | --token <keyId>:<secret> |
| Environment variable | BUGWATCH_AUTH_TOKEN=<keyId>:<secret> |
The environment variable is preferred for CI — set it as a secret in your pipeline so it is not visible in build logs.
The API endpoint can similarly be overridden via --api-url or
BUGWATCH_API_URL (default: https://api.newinstance.cloud).
Command reference
bugwatch symbols upload <path> [options]<path> is the only required positional argument. All metadata flags are optional
but recommended — the debug UUID embedded in every dSYM is the authoritative
match criterion; metadata improves search and display in the dashboard.
Flag reference
| Flag | Env | Type | Default | Description |
|---|---|---|---|---|
| --token | BUGWATCH_AUTH_TOKEN | string | — | Required. API secret key (keyId:secret). Never ship to a browser or app binary. |
| --api-url | BUGWATCH_API_URL | string | https://api.newinstance.cloud | BugWatch REST API base URL. |
| --platform | — | enum | ios | Target platform: ios · macos · tvos · watchos · visionos · catalyst |
| --release | — | string | — | Marketing version (e.g. 1.4.2). Improves dashboard discoverability. |
| --build-number | — | string | — | Build number (e.g. 318). Used to disambiguate the same version string across builds. |
| --bundle-id | — | string | — | App bundle identifier (e.g. com.example.MyApp). |
| --distribution | — | enum | — | app-store · testflight · adhoc · enterprise · development |
| --environment | — | string | — | Environment tag (e.g. production, staging). |
| --commit | — | string | — | Source commit SHA. Links crash reports to a specific code revision. |
| --upload-source | — | string | cli | Tag identifying the upload origin (e.g. ci, fastlane, run-script). Visible in the dashboard. |
Accepted input formats
| Input | Behaviour |
|---|---|
| MyApp.app.dSYM | Single dSYM bundle — packed into a zip and uploaded. |
| build/dSYMs/ (directory) | All *.dSYM bundles found recursively (max depth 4) are packed together into one zip. |
| MyApp.xcarchive | The dSYMs/ subdirectory of the archive is walked; all discovered bundles are packed. |
| symbols.zip | Uploaded as-is (assumed to already contain packed dSYM bundles). |
Pack-and-retry behaviour
- Multiple dSYM bundles are zipped into a single
symbols.zipbefore upload, preserving each bundle's internal directory tree. - The upload is retried up to 3 times on transient failures (HTTP 5xx, HTTP 429, network errors), with 500 ms / 1 000 ms / 1 500 ms back-off between attempts.
- HTTP 4xx errors (auth failure, validation error) are not retried.
- The temp zip is always cleaned up on exit.
REST endpoint called
POST https://api.newinstance.cloud/api/v1/bugwatch/debug-symbolsQuery parameters carry the metadata flags; the body is raw application/octet-stream
(the zip). The response body includes { uploadId, status, duplicate? }.
Verifying the upload
After a successful upload the CLI prints the uploadId:
✓ uploaded: ul_abc123 [QUEUED]You can poll the REST API to confirm indexing is complete:
curl -H "x-api-key: $BUGWATCH_AUTH_TOKEN" \
"https://api.newinstance.cloud/api/v1/bugwatch/debug-symbols/uploads/ul_abc123"status transitions: QUEUED → PROCESSING → AVAILABLE (ready to symbolicate) or
FAILED.
You can also list recent uploads:
curl -H "x-api-key: $BUGWATCH_AUTH_TOKEN" \
"https://api.newinstance.cloud/api/v1/bugwatch/debug-symbols/uploads?limit=10&status=AVAILABLE"Both endpoints require the symbols:read permission scope on the key.
CI integration recipes
Every recipe follows the same four steps:
- Build / archive the app so Xcode produces dSYMs (set Debug Information Format = DWARF with dSYM File for the Release config).
- Expose
BUGWATCH_AUTH_TOKENas a secret env var in the pipeline. - Run
npx @newinstance/bugwatch-cli symbols upload <path> --release <v> --build-number <n>. - Optionally confirm indexing via the REST API or dashboard.
Only the provider-specific wiring differs — see below.
Xcode Run Script build phase
Add a Run Script phase in Xcode (after "Compile Sources"). Set it to run for Release builds only. Xcode provides the dSYM path via a built-in env var.
if [ "$CONFIGURATION" != "Release" ] || [ "$PLATFORM_NAME" = "iphonesimulator" ]; then
exit 0
fi
[ -z "$BUGWATCH_AUTH_TOKEN" ] && {
echo "warning: BUGWATCH_AUTH_TOKEN unset — skipping dSYM upload"
exit 0
}
npx @newinstance/bugwatch-cli symbols upload "$DWARF_DSYM_FOLDER_PATH" \
--release "$MARKETING_VERSION" \
--build-number "$CURRENT_PROJECT_VERSION" \
--bundle-id "$PRODUCT_BUNDLE_IDENTIFIER" \
--upload-source run-scriptStore BUGWATCH_AUTH_TOKEN in Xcode build settings or a non-committed .xcconfig
file — not hard-coded in the script.
Local Xcode (manual archive)
To publish dSYMs from a build you archived on your own Mac (Xcode Product → Archive) — handy for a one-off symbolication fix without wiring up CI:
- Open Window → Organizer → Archives, right-click your archive → Show in Finder.
- Drag the
.xcarchiveinto Terminal to paste its exact path (Release archives contain adSYMs/folder; the CLI walks it for you).
export BUGWATCH_AUTH_TOKEN="<keyId>:<secret>"
npx @newinstance/bugwatch-cli symbols upload \
"~/Library/Developer/Xcode/Archives/2026-06-28/MyApp 2026-06-28 10.30.xcarchive" \
--release 1.4.2 \
--build-number 318 \
--bundle-id com.example.MyApp \
--upload-source local-xcodeThis is the same upload the CI recipes run, just triggered by hand. (You can also point
it at the exported dSYMs/ directory or a single MyApp.app.dSYM.)
Fastlane
After build_app / gym, pass the archive path to the CLI:
build_app(scheme: "MyApp", export_method: "app-store")
sh(
"npx @newinstance/bugwatch-cli symbols upload " \
"'#{lane_context[SharedValues::XCODEBUILD_ARCHIVE]}' " \
"--release '#{get_version_number}' " \
"--build-number '#{get_build_number}' " \
"--upload-source fastlane"
)Set BUGWATCH_AUTH_TOKEN in your Fastfile environment, a .env file, or your
CI's secret store.
GitHub Actions (macOS runner)
jobs:
ios-build:
runs-on: macos-14
env:
BUGWATCH_AUTH_TOKEN: ${{ secrets.BUGWATCH_AUTH_TOKEN }}
steps:
- uses: actions/checkout@v4
- name: Select Xcode
run: sudo xcode-select -s /Applications/Xcode_15.4.app
- name: Archive
run: |
xcodebuild \
-scheme MyApp \
-configuration Release \
-archivePath build/MyApp.xcarchive \
archive
- name: Upload dSYMs to BugWatch
run: |
npx @newinstance/bugwatch-cli symbols upload build/MyApp.xcarchive \
--release "$(agvtool mvers -terse1)" \
--build-number "${{ github.run_number }}" \
--upload-source ciAdd BUGWATCH_AUTH_TOKEN in Settings → Secrets and variables → Actions.
Xcode Cloud
Add a ci_post_xcodebuild.sh script to the root of your repo. Xcode Cloud sets
$CI_ARCHIVE_PATH to the .xcarchive produced by the workflow.
#!/bin/sh
set -e
[ -z "$BUGWATCH_AUTH_TOKEN" ] && exit 0
npx @newinstance/bugwatch-cli symbols upload "$CI_ARCHIVE_PATH" \
--release "$CI_PRODUCT_VERSION" \
--build-number "$CI_BUILD_NUMBER" \
--upload-source xcode-cloudSet BUGWATCH_AUTH_TOKEN in Xcode Cloud → Workflows → Environment → Environment
Variables (mark it Secret).
Bitrise
Add a Script step after the Xcode Archive & Export for iOS step.
Bitrise provides $BITRISE_XCARCHIVE_PATH (the archive) and
$BITRISE_DSYM_DIR_PATH (extracted dSYMs — either works).
npx @newinstance/bugwatch-cli symbols upload "$BITRISE_XCARCHIVE_PATH" \
--release "$BITRISE_APP_VERSION" \
--build-number "$BITRISE_BUILD_NUMBER" \
--upload-source bitriseAdd BUGWATCH_AUTH_TOKEN as a Secret in Bitrise Secrets.
Codemagic
Add a post-build script in your codemagic.yaml:
scripts:
- name: Upload dSYMs to BugWatch
script: |
npx @newinstance/bugwatch-cli symbols upload "$CM_EXPORT_DIR" \
--release "$CM_APP_VERSION" \
--build-number "$CM_BUILD_NUMBER" \
--upload-source codemagic
environment:
BUGWATCH_AUTH_TOKEN: $BUGWATCH_AUTH_TOKENStore BUGWATCH_AUTH_TOKEN in Codemagic → Team / App → Environment variables
(mark it Secret / Secured).
Other macOS CI (GitLab, CircleCI, Jenkins, generic shell)
The command is the same everywhere. The only differences are how you expose the archive path and secret:
- GitLab CI (macOS runner): add a job script step; set
BUGWATCH_AUTH_TOKENas a masked CI/CD variable in Settings → CI/CD → Variables. - CircleCI (macOS executor): add a
runstep; store the token in a Context or project environment variable. - Jenkins (macOS agent): add a shell build step; bind the token from a
Credentials (Secret Text) entry to the
BUGWATCH_AUTH_TOKENenv var. - Any macOS shell:
BUGWATCH_AUTH_TOKEN=<keyId>:<secret> \ npx @newinstance/bugwatch-cli symbols upload <archive> \ --release 1.4.2 --build-number 318
Exit codes
| Code | Meaning |
|---|---|
| 0 | Upload accepted by the server. |
| 1 | Usage or input error (missing path, no token, unsupported file type). |
| 2 | Upload failure (all retries exhausted or server rejected the request). |
Troubleshooting
No dSYM file generated
Confirm the scheme's Release configuration has Build Settings → Debug Information
Format = DWARF with dSYM File. Simulator builds never produce dSYMs — the guard
[ "$PLATFORM_NAME" = "iphonesimulator" ] in the run-script recipe handles this.
UUID mismatch — crash still not symbolicated
The dSYM must come from the exact same build that crashed. Re-upload the dSYM from
that specific archive. Compare UUIDs with dwarfdump --uuid MyApp.app.dSYM.
Missing framework symbols (React Native / Hermes / Flutter / third-party)
Upload the full .xcarchive or the dSYMs/ directory — not just your app's
.dSYM. Each bundled framework ships its own dSYM.
Duplicate upload
The CLI reports (duplicate of an existing upload) and exits 0. Nothing breaks;
the server de-duplicates by UUID.
Archive too large or rejected
Confirm the input is a real .dSYM, .xcarchive, directory, or .zip. The server
enforces a 2 GB size cap on dSYM uploads.
Authentication failure (HTTP 401)
The x-api-key value is wrong, expired, or the key does not have the
symbols:upload scope. Regenerate the key in the dashboard and confirm the scope
is assigned.
HTTP 429 / rate-limited The CLI retries automatically with back-off. If it still fails, wait a moment and re-run — or contact support.
Apple system framework frames unresolved Expected. Your uploaded dSYMs only contain symbols for your app and bundled frameworks. Apple's private OS symbols are resolved separately (or not at all for private-framework frames).
React Native JS or Flutter Dart frames unresolved
Those require source-map or --split-debug-info symbol uploads, which are separate
from dSYMs and handled by a different upload flow.
Links
- BugWatch dashboard: merchant dashboard → your project → Issues / Logs
- REST API Swagger:
https://api.newinstance.cloud/swagger.json - Upload status endpoint:
GET /api/v1/bugwatch/debug-symbols/uploads/{uploadId} - iOS SDK: bug-watch-ios
- Android SDK:
cloud.newinstance:bugwatchon Maven Central - JS/TS SDK:
@newinstance/bugwatchon npm
Package status
| Field | Value |
|---|---|
| Package name | @newinstance/bugwatch-cli |
| Version | 0.1.0 |
| npm published | Yes — 0.1.0 on npm |
| Tests | bun test — 17 passing (discovery, upload retry/redaction, CLI validation) |
| Platforms supported | dSYM only (ios, macos, tvos, watchos, visionos, catalyst) |
| Node requirement | ≥ 18 |
