# dtctl install + auth + skill setup for Claude Code environments

> Source: <https://gist.github.com/dt-benedict/a766ba6fa4c3f87f5f91211703494c9e>
> Published: 2026-08-04 08:47:32+00:00

| #!/bin/bash | |
| # Fail-open: never block session start. | |
| # | |
| # Installs dtctl (dynatrace-oss/dtctl) and configures an authenticated | |
| # context from environment secrets. Both halves must live here (in the | |
| # environment setup script) because the container is ephemeral: the config | |
| # file dtctl writes (~/.config/dtctl/) and its keyring do NOT survive a | |
| # session, so auth has to be reconstructed on every start from durable | |
| # environment variables. | |
| # | |
| # Required environment variables (set these as environment secrets/vars): | |
| # DT_ENVIRONMENT e.g. https://cou8642d.dev.apps.dynatracelabs.com | |
| # IMPORTANT: dtctl talks to the PLATFORM / Grail query API, | |
| # which is on the .apps host. This is the OPPOSITE of the | |
| # CLAUDE.md OTLP-ingest rule (OTLP goes to the non-.apps | |
| # host). Do not confuse the two: query -> .apps, ingest -> std. | |
| # DT_API_TOKEN a dt0c01.* (classic) or dt0s16.* (platform) token | |
| # with query read scopes: storage:logs:read, | |
| # storage:metrics:read, storage:events:read, | |
| # storage:spans:read, storage:buckets:read | |
| set -uo pipefail | |
| # --- Install (idempotent) -------------------------------------------------- | |
| # NOTE: `go install` does NOT work for dtctl — its go.mod uses replace | |
| # directives, which Go unconditionally refuses for `go install`. Build from | |
| # source instead (replace directives are allowed in the main module). | |
| # GOTOOLCHAIN=auto pulls the required Go toolchain (dtctl needs >= 1.26.5). | |
| if ! command -v dtctl >/dev/null 2>&1; then | |
| DTCTL_BUILD_DIR=$(mktemp -d) | |
| ( | |
| git clone --quiet --depth=1 https://github.com/dynatrace-oss/dtctl "$DTCTL_BUILD_DIR/src" \ | |
| && GOTOOLCHAIN=auto go build -C "$DTCTL_BUILD_DIR/src" -o /usr/local/bin/dtctl . | |
| ) || echo "dtctl install failed; setup will retry next session start" | |
| rm -rf "$DTCTL_BUILD_DIR" 2>/dev/null || true | |
| fi | |
| # --- Authenticate (idempotent) --------------------------------------------- | |
| # Rebuild the context each session from the stored secret. Uses file-backed | |
| # token storage since no OS keyring is available in the container. | |
| export DTCTL_TOKEN_STORAGE=file | |
| if command -v dtctl >/dev/null 2>&1; then | |
| if [ -n "${DT_ENVIRONMENT:-}" ] && [ -n "${DT_API_TOKEN:-}" ]; then | |
| dtctl config set-credentials dynadev --token "$DT_API_TOKEN" >/dev/null 2>&1 \ | |
| && dtctl config set-context dynadev \ | |
| --environment "$DT_ENVIRONMENT" \ | |
| --token-ref dynadev \ | |
| --safety-level readonly >/dev/null 2>&1 \ | |
| && dtctl config use-context dynadev >/dev/null 2>&1 \ | |
| && echo "dtctl context 'dynadev' configured (readonly)" \ | |
| || echo "dtctl auth config failed; check DT_ENVIRONMENT / DT_API_TOKEN" | |
| else | |
| echo "dtctl installed but not authenticated: set DT_ENVIRONMENT and DT_API_TOKEN env secrets" | |
| fi | |
| # --- Install the dtctl AI-assistant skill (idempotent) ------------------- | |
| # dtctl embeds its own skill (SKILL.md + references). Reinstall each session | |
| # so the skill tracks the installed dtctl version. --global lands in | |
| # ~/.claude/skills/dtctl regardless of cwd; both that dir and the repo's | |
| # .claude/ are ephemeral (.claude/ is gitignored here), so this must run | |
| # on every start rather than being committed. | |
| dtctl skills install --for claude --global --force >/dev/null 2>&1 \ | |
| && echo "dtctl skill installed for claude (~/.claude/skills/dtctl)" \ | |
| || echo "dtctl skill install failed" | |
| fi | |
| command -v dtctl >/dev/null 2>&1 && dtctl version || true |
