Overview
Use this runbook when you deploy Beacon with Kandji, want all Beacon telemetry in a Google Cloud Storage (GCS) bucket, and your Macs already send Claude Code and Codex CLI OpenTelemetry (OTLP) to another backend, such as a token-usage or cost platform, that must keep receiving it. Claude Code and Codex each export OTLP to one endpoint. Beacon needs that export to arrive at its local collector, so installing Beacon points both runtimes at127.0.0.1:4317, and the runtimes stop sending to the backend you already had. This setup keeps both destinations fed:
The two are independent: the GCS forwarder reads Beacon’s local logs, and the forward runs inside the collector. Either keeps working when the other’s destination is down, and repairs and package upgrades keep both.
Kandji provides the deployment layer:
The OTLP forward needs the first Beacon release after v1.3.51. An older package does not have the
--otlp-forward-* flags, and its collector has no OTLP exporter. GCS forwarding works with current packages.What the existing backend receives
The forward has its own collector pipelines, fed by the same receiver as Beacon’s. The backend receives the OTLP logs, metrics and traces Claude Code and Codex send, in OTLP form rather than Beacon’s event schema, with three differences from the raw export:- Only Claude Code and Codex. Other OTLP the collector receives stays local (it still reaches GCS through Beacon’s log): Gemini CLI, VS Code, SDK apps, and Beacon’s browser extension, which carries full claude.ai and ChatGPT chats.
--otlp-forward-harnessnames a different list. - Secrets redacted. Credentials found in prompts, commands and tool arguments are replaced with
[REDACTED], the same redaction Beacon applies before Splunk and Falcon. Token counts, cost, models and session ids are untouched. - No raw API bodies. Claude Code API body events get the same handling as in Beacon’s own log.
Prerequisites
- A signed and notarized
BeaconEndpointAgent-<version>-arm64.pkg, from the first release after v1.3.51. It includes/opt/beacon/bin/vector, which the GCS forwarder uses. The endpoint package is for Apple Silicon Macs. - A pilot Kandji Blueprint, or an assignment rule on each library item, that targets a few Macs with Claude Code or Codex CLI installed.
- A Google Cloud project where you can create a bucket and service accounts, and a policy decision that permits a service-account key on managed Macs. The packaged Vector authenticates to GCS with a service-account JSON key; Workload Identity Federation and interactive Application Default Credentials are not supported for this LaunchDaemon.
- The OTLP settings your Macs use for the existing backend today:
- Endpoint: for gRPC,
host:portor a URL; for HTTP, the base URL without/v1/logs. - Protocol:
grpcorhttp/protobuf. - Headers: usually an API key or authorization header, from
OTEL_EXPORTER_OTLP_HEADERSin Claude Code’s settings orheadersin Codex’s[otel.exporter.*]table.
- Endpoint: for gRPC,
- A security decision that prompt, command, tool, file, model and inventory telemetry may be written to your GCS bucket.
Step 1: stop the old direct export from fighting Beacon
How you rolled out the existing backend decides what to change. Find the one that matches. Claude Code managed settings (/Library/Application Support/ClaudeCode/managed-settings.json, delivered by a Kandji profile or script). Managed settings take precedence over the user’s ~/.claude/settings.json, where Beacon writes its endpoint, so Claude Code would keep exporting straight to the backend and Beacon would get no Claude OTLP. In the managed settings env:
- Set
OTEL_EXPORTER_OTLP_ENDPOINTtohttp://127.0.0.1:4317andOTEL_EXPORTER_OTLP_PROTOCOLtogrpc. - Remove
OTEL_EXPORTER_OTLP_HEADERS, everyOTEL_EXPORTER_OTLP_{LOGS,METRICS,TRACES}_ENDPOINTand_HEADERS, andBETA_TRACING_ENDPOINT. The backend’s credentials move to the forward in Step 5. Left in place, they would be sent to the local collector instead.
~/.claude/settings.json or ~/.codex/config.toml. Beacon rewrites the OTLP settings in both files on every install, upgrade and repair, and replaces Codex’s whole [otel] table. Retire or unscope the Kandji item that writes the backend’s OTLP settings to those files; otherwise it and Beacon overwrite each other on every run.
Step 2: prepare Google Cloud
Beacon’s GCS Terraform module creates a dedicated bucket, an upload-only writer service account limited to the Beacon prefix, and an optional read-only service account for checking deliveries:main.tf
roles/storage.objectCreator: it can create objects and cannot list, read, overwrite or delete them. Validate uploads with the reader. To do the same with gcloud, or to add lifecycle rules or CMEK, see GCS with Terraform and the gcloud steps in Rippling and GCS.
Create the writer’s key on an administrator workstation:
Step 3: deliver the GCS writer key with its own Custom App
Kandji has no secret store, and Kandji admins can read script text, so do not paste the key into a script. Package it as its own installer and deploy that as a Custom App. The key then lives at a fixed path outside Beacon’s directories, and rotating it is a new key package, with no change to Beacon. The endpoint path used in the rest of this guide (replace the organization name):--ownership recommended installs the files as root:wheel with the modes above. Sign the package with your Developer ID Installer certificate (productsign) if your Kandji setup requires signed packages. Delete the local key and payload once the package is uploaded, and restrict who can download it from Kandji.
In Kandji, add a Custom App named Beacon GCS writer key, install type Package, upload the package, choose Install once per device, and assign it to the pilot Blueprint.
Step 4: add the Beacon package as a Custom App
Add another Custom App:- Name it
Beacon Endpoint Agent. - Set the install type to Package and upload the Beacon
.pkg. - Choose Install once per device (or self-service for the pilot).
- Leave the pre-install script empty.
- Paste the post-install script from Step 5.
- Assign it to the pilot Blueprint.
/opt/beacon, writes the system endpoint configuration, starts com.beacon.endpoint.collector, and points the logged-in user’s Claude Code and Codex at the collector (~/.claude/settings.json and ~/.codex/config.toml), with Beacon’s Claude Code and Codex hooks.
Step 5: turn on the OTLP forward (post-install script)
Use this script as the Beacon Custom App’s post-install script. It turns the forward on as soon as Beacon is installed, before the GCS key may have arrived. Replace the three values at the top with the endpoint, protocol and headers from the prerequisites:Beacon - OTLP forward (post-install script)
beacon endpoint repair stores the forward in /Library/Application Support/Beacon/Endpoint/config.json and renders it into the collector config next to it. Header values are usually credentials, so they go to a root-only file beside it, otlp-forward-headers.json (0600), and into the collector config, which is also 0600. config.json keeps only the header names and stays readable, because the logged-in user’s beacon commands read it to find the system log. Package upgrades and later repairs, including the GCS helper’s, keep the forward; you do not pass the flags again. To remove it, run beacon endpoint repair --system --no-otlp-forward.
Flags you may also need:
This script holds the backend’s API key, and Kandji admins can read Custom App scripts. If that is not acceptable, deliver the key the way Step 3 delivers the GCS key, as a root-only file in its own package, and read it into
OTLP_FORWARD_HEADERS here and in Step 6. Beacon never prints header values: beacon endpoint status and beacon endpoint doctor list header names only.Step 6: configure GCS and keep everything applied (Custom Script)
Add a Custom Script library item, set it to Run every 15 minutes during rollout (daily once the pilot is stable), and assign it to the same Blueprint. Its remediation configures the GCS forwarder once the key has arrived, and both scripts together repair anything that drifts. Audit script: exits non-zero, which runs the remediation, when the GCS forwarder is not running, the forward is missing, or the logged-in user’s runtimes are not pointed at the collector:Beacon - GCS and OTLP forward (audit)
Beacon - GCS and OTLP forward (remediation)
repair-hooks-and-forwarder.sh, under /opt/beacon/jamf for compatibility; it calls no Jamf APIs):
- checks the bucket, Vector and the key, which must be owned by root with mode
0400or0600; - writes and validates
/Library/Application Support/Beacon/Forwarders/gcs-vector.tomland startscom.beacon.endpoint.gcs-forwarder, which uploads the runtime log under<prefix>/runtime/and the inventory log under<prefix>/inventory/; - repairs the endpoint and installs Claude Code hooks for the logged-in user, keeping the forward from step 1.
BEACON_VECTOR_READ_FROM=end), so history from before the forwarder started is not uploaded; inventory starts at the beginning so the first snapshot is not missed. When nobody is logged in, the forwarder is configured but the hook step fails, so the script reports a failure until a user logs in and the next run completes.
Validate a pilot Mac
1. Services
"configured":true with your endpoint and protocol, and an otlp/forward (gRPC) or otlphttp/forward (HTTP) exporter in the collector config.
2. Runtime settings
As the logged-in user:127.0.0.1:4317. If Claude Code has managed settings, check that /Library/Application Support/ClaudeCode/managed-settings.json points there too (Step 1).
3. One session reaches all three places
Restart Claude Code and Codex so they read the new settings, then:- Local log: the marker from both runtimes.
- Existing backend: the same sessions, with token usage from both runtimes, in the usual time window.
-
GCS: within about five minutes (Vector’s batch timeout), new runtime objects. Check with the reader identity, since the writer cannot list:
Impersonating needs
roles/iam.serviceAccountTokenCreatoron the reader service account.sudo /opt/beacon/bin/beacon endpoint gcs validate --systemwrites a validation event you can look for the same way.
Troubleshooting
The existing backend stopped receiving data
- Check the forward is configured (validation step 1). If not, run the Step 6 remediation.
-
Read the collector’s log for export errors from the forward:
Unauthenticatedor401means a wrong or missing header;connection refused,deadline exceededor a TLS error means the endpoint, protocol, or--otlp-forward-insecure/--otlp-forward-ca-fileis wrong. HTTP endpoints take the base URL; the exporter adds/v1/logs,/v1/metricsand/v1/traces. -
If status shows
"headers_missing": true, the header values file (otlp-forward-headers.json) was removed, for example by a manual cleanup, and the forward is sending without credentials. Run the Step 6 remediation. - Check that the runtimes still point at the collector (validation step 2). A runtime update or an old Kandji item can rewrite the settings; the Step 6 audit catches it on its next run.
No GCS objects arrive
- Is the forwarder running?
sudo launchctl print system/com.beacon.endpoint.gcs-forwarder. If not, read the remediation’s log,/var/root/Library/Logs/Beacon/kandji-gcs-reconcile.log. - Vector’s own errors:
sudo tail -n 100 /Library/Logs/Beacon/com.beacon.endpoint.gcs-forwarder.err. A403means the key belongs to the wrong service account, the writer lacksroles/storage.objectCreatoron the bucket, or (with CMEK) cannot use the key. - The key:
sudo stat -f 'owner=%Su mode=%Lp' "<key path>"should showowner=rootand mode400or600. If the key Custom App has not installed, the remediation reports “GCS writer key not delivered yet”. - Runtime upload starts at the end of the log. Generate new activity (or
beacon endpoint gcs validate --system) and wait up to five minutes. - Listing with the writer fails by design; use the reader.
The remediation fails with no console user
The Claude Code hooks are per-user, so the GCS helper fails until someone is logged in, after configuring the forwarder. Keep the Custom Script recurring; the next run after login completes.repair rejects the forward flags
unknown flag: --otlp-forward-endpoint means the installed beacon predates OTLP forwarding, and collector ... has no "otlp" exporter means beacon-otelcol does (for example, an older one passed with --collector). Deploy the Beacon package from the first release after v1.3.51, then run the Step 6 remediation.
Beacon has no Claude Code OTLP events, but hooks work
Claude Code managed settings still point at the old backend. See Step 1.Codex metrics are missing in the backend
Beacon’s Codex settings export logs and traces only. Codex token usage reaches the backend in the turn traces. If the backend can only read Codex metrics, contact Asymptote.Rotating credentials
The Step 6 audit checks that the forwarder runs and that a forward exists, not which key either uses, so it does not notice a rotation. Push the new key, then run the Step 6 remediation once on each Mac, for example as a separate Custom Script set to Run once per device:- GCS writer key: create a new key and ship it as a new version of the Step 3 package, at the same path. The forwarder reads the key when it starts, and the remediation restarts it. Once uploads continue, delete the old key in Google Cloud.
- Backend API key: update the header in the Step 5 and Step 6 scripts. The remediation replaces the stored headers and restarts the collector.
Related
GCS with Terraform
The bucket, writer and reader the GCS forwarder uses.
beacon endpoint repair
Every repair flag, including the OTLP forward.
Claude Code
Claude Code endpoint telemetry coverage.
Codex CLI
Codex CLI endpoint telemetry coverage.