Skip to content

Releases: Dynatrace/dtctl

v0.45.0

Choose a tag to compare

@github-actions github-actions released this 21 Oct 18:32
97f26e0

This release adds get actions and describe action to discover the actions installed apps declare, and moves to Go 1.26.10.

✨ Highlights

Discover app actions: get actions and describe action (#715)

List and inspect the actions installed apps declare, read from the App Registry's /actions API (early adopter). Useful for workflow authors and AI agents.

dtctl get actions                          # list all actions
dtctl get actions --name <text> --app <app-id>
dtctl get actions --filter '<filter>'      # passed through verbatim, nested paths included
dtctl describe action <app-id>:<action-name>
  • describe action shows every attribute the manifest declares, e.g. docs.inputSchema, docs.resultSchema and docs.guide, inline in every output format.
  • Development tier. The API isn't deployed on every environment yet, so both commands are registered only when opted in: DTCTL_DEVELOPMENT=actions or dtctl config set development.actions on.

🔧 Maintenance

  • Go 1.26.10 in go.mod, sdk/go.mod and all workflows (#719). Also shipped as v0.44.1.
  • Dependency updates: toon-go 1.0.1, x/term 0.47.0, x/text 0.43.0, OpenTelemetry OTLP trace exporter, dbus 5.2.4 (SDK), plus the codeql-action, goreleaser-action, scorecard-action and create-github-app-token CI actions.

Full Changelog: v0.44.1...v0.45.0

v0.44.1

Choose a tag to compare

@github-actions github-actions released this 21 Oct 15:01

0.44.1 (2026-10-21)

Bug Fixes

v0.44.0

Choose a tag to compare

@github-actions github-actions released this 20 Oct 10:41
16bea52

This release adds two OAuth scopes at login, enabling code-level profiling and classic-to-3rd-gen dashboard migration through dtctl exec api and dtctl-* plugins. It also fixes the GCP preview commands skipping the root's global checks.

✨ Highlights

New scopes at dtctl auth login (#698, #699)

No safety level requested these before, so calls needing them got a 403 whatever the IAM policy allowed.

Scope Unlocks Requested by
storage:profiles:read (#698) Code-level profiling data (code-level analysis API, dtctl-* plugins) readonly, readwrite-all, dangerously-unrestricted
environment-api:dashboard-3rdgen-migration:execute (#699) Converting a classic dashboard to 3rd gen every safety level
  • The migration endpoint only returns the converted dashboard and saves nothing, so readonly can request it. Storing the result still needs document write scopes.
  • All dtctl OAuth clients are registered for both scopes. The IAM policy is still a separate gate.
  • Existing sessions need a fresh dtctl auth login. Platform tokens are unaffected.

🐛 Fixes

  • GCP preview commands run the root's pre-run checks (#696) — the 17 * gcp * commands skipped --dry-run rejection and --jq normalisation, so get gcp connection --dry-run called the API instead of exiting 2 like get aws connection --dry-run. The Preview notice now prints only after the command line is accepted.

Full Changelog: v0.43.0...v0.44.0

v0.43.0

Choose a tag to compare

@github-actions github-actions released this 13 Oct 16:13
0694800

This release adds an opt-in local tally of the DQL data dtctl scans (dtctl usage), points agents at the Dynatrace for AI companion skills, and moves TOON output to the 2.0 format. It also ships Go 1.26.9 for the latest govulncheck fixes.

✨ Highlights

See what your scripts and agents scan: dtctl usage (#693)

Turn it on per context and dtctl keeps a running tally of every DQL execution it runs, failed ones included:

dtctl config set-context <context> --usage-tracking --global
dtctl usage                 # today, last 7 days, last 30 days for the current context
dtctl usage --all-contexts
dtctl usage reset           # clear the current context (--all-contexts: delete the file)
  • Off by default. With tracking off, nothing is written.
  • Counters only. Per day and per context: queries, failed queries, scanned bytes, records and data points. No query text, results, identity or tokens are stored. The file is usage.json in the XDG state dir (mode 0600), updated atomically under a lock; days older than 90 are pruned. A tally that can't be written never fails or slows the command.
  • Global config only. Like aliases and hooks, a project-local .dtctl.yaml can't opt a context in: dtctl usage warns when it ignores one, and set-context --usage-tracking refuses to write to a local config.
  • Not a bill. Scan volume is shown in GiB/MiB and counts only what dtctl ran on this machine. For authoritative consumption, query the billing events.

The command is experimental.

Agents find the companion skills (#692)

The dtctl skill gains a short "Companion skills" section naming dt-dql-essentials, dt-obs-* and dt-app-*. When dt-dql-essentials isn't installed, dtctl skills install prints npx skills add dynatrace/dynatrace-for-ai (on stderr, or in context.suggestions in agent mode). It never changes the exit code.

Changes to be aware of

  • TOON output uses the 2.0 format (#686). -o toon array headers no longer carry the [#N] length marker. If you parse TOON output, check your parser.

🔧 Maintenance

  • Go 1.26.9 and golang.org/x/net 0.60.0 clear the current govulncheck findings (#691).
  • Dependency updates: toon-go 0.1.0, OpenTelemetry 1.47.0, x/sync 0.24.0, x/sys 0.49.0 (SDK), plus the setup-python and sbom-action CI actions.

Full Changelog: v0.42.0...v0.43.0

v0.42.0

Choose a tag to compare

@github-actions github-actions released this 08 Oct 21:48
f4102b1

This release makes agent mode correct its own mistakes: failed DQL comes back rewritten, guessed commands point to real ones, and empty results explain themselves. It also lets auth login store tokens in a file on headless Linux (with consent), and stops dtctl from retrying POSTs that may already have run.

✨ Highlights

Agent mode repairs common mistakes (#672, #677)

Agents keep making the same few mistakes, so the response now carries the fix:

  • Failed queries come back rewritten. SQL and Splunk habits (stats … by, single quotes, function and command synonyms, aggregation references, a time window outside fetch) are translated into a query that runs.
  • Guessed commands name real ones. dtctl workflows, get problems or slo-status list the commands that read that resource instead of an unrelated near-match. A fetch of a platform object (workflows, SLOs, dashboards) points to dtctl get.
  • Empty results explain themselves. When a filter compares a field to a string, the response lists the values the field really has (context.empty_reason: value_not_found).
  • Results say what they are. context.window states the window searched (and notes the default 2h), an unsorted | limit N is labelled a sample, duration columns are labelled nanoseconds, and get slos points to dtctl exec slo for status.
  • --jq without -o returns JSON instead of an auto-chosen CSV/YAML string.
  • exec analyzer is trimmed. It drops the echoed input and nulls and summarizes series (--series, --precision, default summary and 4 digits).

Output outside agent mode is unchanged.

File token storage on headless Linux (#678)

With no Secret Service, auth login failed until you exported DTCTL_TOKEN_STORAGE=file in every shell. At a terminal, dtctl now asks once whether to store tokens in a file (mode 0600) and remembers the answer. It never switches silently: a locked or broken keyring, CI, non-TTY runs and --agent never prompt, and DTCTL_TOKEN_STORAGE still wins. dtctl doctor shows a remembered choice and how to undo it.

Opt-in concurrent execution for embedders (#676, #680)

pkg/engine gains engine.New and engine.WithConcurrentExecution(n), which run up to n invocations in parallel, each on its own command tree. Nothing is concurrent by default; the CLI, dtctl serve and existing callers behave as before. See docs/dev/CONCURRENT_EXECUTION.md.

🐛 Fixes

  • POST requests are no longer retried on server errors (#660, fixes #276) — a 502, 504 or dropped connection can arrive after the server already acted, so the retry could start a second workflow run, create a duplicate object or run a DQL query twice. Only idempotent methods (GET, HEAD, OPTIONS, TRACE, PUT, DELETE) are retried.
  • Browser login now sends resource on the code exchange and on refresh (#652) — without it, account APIs such as service-users answered 401 for tokens from a browser login.
  • auth status and doctor report the real token store (#661, fixes #393) — a keyring that answers reads but refuses writes (macOS security exit 44) was reported as the backend after dtctl had fallen back to the file store. doctor now warns about that case.
  • exec api and describe api --raw no longer append a newline (#651, fixes #643) — every binary download (a ZIP, say) was one byte longer than the response body, so its SHA-256 never matched the endpoint's /checksum.
  • --spill=auto measures what is actually written (#658, fixes #570) — the threshold was compared against the 2-space-indented size even when the envelope goes out compact (about 2.5× too large), and measured_bytes overstated what the agent received.
  • Remaining dry runs emit an agent envelope (#663, fixes #514) — exec api --dry-run printed prose in agent mode, and the account, create and cloud monitoring dry runs gave agents nothing to parse. They now return a JSON envelope.
  • Document UI links no longer carry a doubled slash (#655, fixes #653) when the environment URL ends in one.
  • The Nix flake builds with nixpkgs Go (#670).

📚 Documentation

  • The dtctl skill reference steers DQL text filters away from lower() (#671).

Full Changelog: v0.41.0...v0.42.0

v0.41.0

Choose a tag to compare

@github-actions github-actions released this 01 Oct 11:59
3e845a5

This release stops large queries from exhausting memory, adds claim environment-share for opening a shared document from its link, and fixes cloud monitoring updates that silently dropped fields.

✨ Highlights

Large query results stream to disk (#631, #633, closes #466)

dtctl query held the raw response, a decoded copy and the row slice at once, so peak memory ran 10-14× the output size and sandboxes OOM-killed it on the spill path. -o jsonl, --spill-to <file>.jsonl and agent mode's automatic spill now decode each row straight to disk. Measured on a 500k-row / 164 MB result:

peak RSS
v0.40.0 1,806 MB
v0.41.0 41 MB

Formats that need the whole result (table, csv, parquet, yaml, toon, --jq, --typed, live mode) keep the buffered path. Small results are byte-identical. In agent mode, a streamed result sets context.streamed: true and omits context.measured_bytes.

Claim a shared document from its link (#648, #649, closes #644)

dtctl claim environment-share <share-id | share-url>

Claims the share as the browser does when you open a #share= link, and prints the document ID and type. It fails early if the link belongs to another environment, supports --dry-run, and is experimental. It requests the new document:environment-shares:claim scope, granted from readwrite-mine up.

Cloud monitoring updates keep unmodelled fields (#620, fixes #608, #516)

enable, disable, update, edit and apply on AWS, Azure and GCP monitoring configs dropped every field dtctl doesn't model. On GCP that reset logsConfiguration and turned log ingestion off. Those fields now round-trip, so new schema fields survive without a dtctl release.

Approximate results say so (#636, #635)

A successful but approximate Grail result looked exact. metadata.approximations and metadata.notifications are now shown, and each approximation prints a Warning: Result is approximate: … on stderr or in context.warnings. --include-types also warns when the output format can't carry types.

🐛 Fixes

  • --admin-access works with OAuth login (#618, fixes #375) — readwrite-all now requests document:documents:admin.
  • --check-scopes accepts any-of scopes (#617, fixes #571) — a token with only platform-management:environments:read no longer fails with exit 5.
  • Cancelled queries stop on Grail (#621, fixes #502) — in dtctl serve and the embedded engine, cancelling a request now cancels its DQL query.
  • apply --dry-run for settings matches apply (#616, fixes #521) — a missing object dry-runs as created.
  • Launchpads and custom document IDs (#634) — launchpads print their UI URL, and delete|describe|edit|history|restore document <slug-id> resolve a non-UUID as an ID first.
  • Intent catalog (#637, refs #517) — plural schemas are read, acceptedKeys and matchedKeys are added, and an undeclared type is "" instead of "string".
  • Empty required flag values exit 2 (#638) — previously a runtime error with exit 1.
  • Agent detection is deterministic (#622) — the name was random when two variables were set, and several agents were never detected.
  • Server mode reuses upstream connections (#639) — one shared transport instead of a TLS handshake per request.
  • CurrentContextObj writes persist (#614, fixes #543), and commands lists scopes for read-only resources under mutating verbs (#648).

📚 Documentation

  • Agent-mode output changes don't need a feat!: title (#640); exec api stays stable (#615).
  • Setup for Claude Code on the web in docs/CONFIGURATION.md (#624).

Full Changelog: v0.40.0...v0.41.0

v0.40.0

Choose a tag to compare

@github-actions github-actions released this 25 Sep 06:00
efd558d

This release makes agent mode token-optimal by default. dtctl query and the get list verbs now return lean output to agents: minimal metadata, compacted rows, summarized timeseries, clipped long strings, a size-appropriate encoding, and a first page of 50 items. Every lean default has a flag that restores the previous output exactly. Output outside agent mode does not change.

It also closes a safety hole: --dry-run is now opt-in per command, so a command that cannot dry-run rejects the flag instead of doing the real work. SDK errors get their documented error codes and exit codes back, and DQL errors now say where the query broke and how to fix it.

✨ Highlights

Token-optimal agent defaults (#589, #591, #594, #595, #596, #597, #599)

Agent mode exists for callers that pay per token, yet its defaults were the most expensive output dtctl could produce. Measured on real tenant data:

  • The Grail metadata block made up 39% of a small query result.
  • Printing constant columns once instead of on every row made 20 log records 4.5× smaller (50,380 → 11,139 bytes).
  • A timeseries summary was about 9× smaller than the raw arrays, and most agent questions only need the shape.
  • One oversized field, like a stack trace, could fill a context window.

Each saving already existed behind a flag that most agents never found. They are now the agent-mode defaults:

New agent-mode default Opt-out that restores the previous output
query encoding (#589, #599) -o auto: flat, uniform rows come back as CSV, nested documents as YAML or JSON; context.format names the choice any explicit -o, e.g. -o json
query metadata (#591) --metadata=minimal: only the fields an agent acts on --metadata=all
query timeseries (#594) --series=summary --precision 4: min/max/avg/last and a sparkline instead of raw arrays, 4 significant digits --series=full --precision 0
query rows (#595) --compact: nulls dropped, columns that are constant across all rows moved to result.constant --compact=false
query strings (#597) --max-field-chars 500: longer values clipped with a …(+N chars) marker --max-field-chars 0
get lists (#596) first 50 items; context.total and context.has_more say how many exist --limit 0

When a default actually changed a result, the envelope carries one short context.suggestions entry naming the opt-out. When it changed nothing, nothing is added. Full data is always one flag away, and spilled results keep their full contents on disk.

New opt-in controls ship alongside:

  • --max-output-bytes / --max-output-tokens bound the whole inline result, keeping the leading rows that fit.
  • --series=downsample:N keeps up to N extreme-preserving points.
  • get --limit and --fields work on every list verb, flattening nested fields into columns for tabular formats (--fields id,name,modificationInfo.lastModifiedTime).

DQL errors that say how to fix the query (#598)

A parse error now carries error.position and a caret-marked error.snippet of the offending line. For known traps, a runnable corrected query comes first in error.suggestions:

"snippet": "fetch logs | summarize count() by service.name\n                               ^^",
"suggestions": ["group with the by: parameter, by:{field, …}, not a trailing by keyword: dtctl query 'fetch logs | summarize count(), by:{service.name}'"]

Queries fail fast, and empty results explain themselves (#590, #593, #600)

  • Scope precheck. query checks a DQL statement's storage scopes before sending it, and fails with insufficient_scope, naming the missing scopes and what the token can read. Agents no longer fan out into the same NOT_AUTHORIZED_FOR_TABLE over and over. Single-quoted strings in the query are ignored by the check (#600).
  • Empty-result diagnosis. An empty result no longer leads straight to "widen the time window". dtctl first runs one bounded probe and reports field_not_in_sample (a filtered or grouped field doesn't occur in the data) or metric_not_in_window (the metric key has no data in the window) in context.empty_reason.

share --no-notify (#569)

share dashboard|notebook|document emailed every recipient, and a share with a group emailed every member. --no-notify suppresses the notification. The default is unchanged.

⚠️ Breaking changes

Agent-mode output changed shape (#589, #591, #594, #595, #596, #597, #599). Callers that parse agent-mode query output structurally and pass no format or shaping flags will see:

  1. result.records can be a string. Under -o auto it holds CSV or YAML text, and context.format names it. This is the sharpest edge. → Pin -o json, or branch on context.format.
  2. Rows lose constant columns and nulls. A column that is constant across all rows now lives in result.constant. → Merge result.constant into each row, or pass --compact=false.
  3. Numbers are rounded and series are summarized. → Pass --series=full --precision 0 to recompute from raw values.
  4. get lists stop at 50 items. → Check context.has_more, or pass --limit 0.

None of these produce an error. To get the previous agent output back exactly, use -o json -M=all --series=full --precision 0 --compact=false --max-field-chars 0 for query, and --limit 0 for get lists. Interactive and scripted use without agent mode is unaffected.

These changes follow a policy that is now written into dtctl (#603). Agent-mode output is shaped for agents and may change in any minor release, stable commands included. The envelope skeleton (ok, error.code, result.kind, the context keys) stays additive-only, and explicit flags keep their meaning. A program that needs a fixed shape pins the flags.

--dry-run is opt-in per command (#561, fixes #477). --dry-run used to be a global flag that every command accepted, and most delete, restore and exec commands ignored it and did the real work: dtctl delete bucket <name> --dry-run -y deleted the bucket. The flag now exists only on commands that implement it. Elsewhere it is a usage error (exit 2) that points at dtctl verify.

  • delete and restore now implement it: they resolve the target, print what they would do, and send no mutating request.
  • exec has no dry run, except exec api.
  • A dry run is gated like a read by the safety checks, so previews work in readonly contexts.
    → If a script passed --dry-run to a command that doesn't support it, it was already running for real. Remove the flag, or switch to a command that supports it.

SDK HTTP errors map to the documented error codes (#558). Every HTTP failure from an SDK call used to come back as the generic code error with exit 1. It now returns auth_required, permission_denied or not_found with status_code, and exits 3, 5 or 4, as docs/AGENT_MODE.md always documented. Unknown-flag errors now keep their usage exit code 2 as well (#561).
→ If you branched on exit code 1 for every API failure, handle 3/4/5.

For Go consumers of the CLI module (not sdk/): pkg/client.APIError, NewAPIError, diagnostic.Wrap and diagnostic.WrapWithMessage are removed. Use *httpclient.APIError with errors.As.

🔒 Security and safety

  • Remaining unescaped resource IDs (#559) — #545 in v0.39.0 missed 18 call sites, including four DELETEs where the base path lived in a constant, so describe segment "<id>#x" showed a different segment than the one named. It also left : unescaped, so describe analyzer "foo:poll" polled the analyzer foo. All 131 interpolated path values now go through one escape that also encodes :, and the build check now covers constant base paths.
  • Release signatures moved to a Sigstore bundle (#610) — checksums.txt is now signed with cosign v3 into checksums.txt.sigstore.json, replacing checksums.txt.sig. Verify with cosign verify-blob --bundle checksums.txt.sigstore.json --certificate-identity-regexp 'https://github.com/dynatrace-oss/dtctl/' --certificate-oidc-issuer https://token.actions.githubusercontent.com checksums.txt.
  • Empty flag values no longer fall back silently (#557) — --context "$CTX" with CTX unset ran against the default context and exited 0. Required flags (--task, --schema, --scope, …) passed with "" too. An explicitly empty value is now a usage error that names the flag.

🐛 Fixes

  • -f - reads stdin everywhere (#556, #607, fixes #563) — apply, create, update, exec dql and the rest read a file literally named -. If one existed they applied it and reported success. Every --file input now goes through one stdin-aware reader, a guard test keeps it that way, and reading stdin from a terminal is refused instead of hanging.
  • Failed, cancelled and expired queries were reported as successes (#560) — a FAILED state from a synchronous execute, or CANCELLED / RESULT_GONE from the poll loop, returned zero records with exit 0. They are now errors (query_failed, query_cancelled, result_expired). An unknown future state keeps polling and is then reported as unknown_query_state.
  • create gcp monitoring broke Smartscape discovery (#604, fixes #573) — without --locationFiltering it set every location from the extension schema instead of none, and the GCP poller then failed with "Query has too many alternations". The default is now no filter, as in the UI. update gcp monitoring --locationFiltering all repairs configs created by earlier versions. The loss of logsConfiguration on update/apply is tracked in #608.
  • logs workflow-execution --tasks/--all hid failed fetches (#606, fixes #499) — a task log that couldn't be fetched was folded into the output, and the command exited 0. It now prints what it fetched and exits 1 with an error that names the failed tasks: task_log_unavailable in agent mode, with a `--tas...
Read more

v0.39.0

Choose a tag to compare

@github-actions github-actions released this 18 Sep 21:30
65ff0de

This release makes dtctl's command surface contractual: every command now declares what it promises about its own shape, and a context can be pinned to only the surface you are willing to depend on. Alongside that — windowed ingest verification, per-context query limits, non-interactive OAuth login for CI, and a URL-escaping fix worth reading before you upgrade.

✨ Highlights

Command stability tiers (#485, #540)

stable used to be what a command got for saying nothing. That made the strongest promise dtctl makes — additive-only, removal only through a deprecation cycle — the one tier nobody chose: a command shipped an additive-only contract because its author had not considered the question, and by the time anyone noticed, callers had built on it.

Every command now declares its own tier where it is built (272 explicit declarations), and a lint refuses an undeclared one.

Tier Badge Guarantee
stable (none) Additive-only from the release that declared it; removal only through a deprecation cycle.
experimental [Experimental] Usable and supported, but the shape may change in a minor release. Not safe for unattended automation.
development [Development] Not registered at all unless explicitly opted in. No guarantees.

A context can then be pinned to the surface it is allowed to reach:

dtctl config set-context prod --min-stability stable
dtctl config set-context prod --stability-exception 'query --decode-snapshots'
DTCTL_MIN_STABILITY=stable dtctl get workflows      # tighten one process

The floor lives on the context, not the profile — profiles are reusable topical sets, and a profile that encoded a tier would propagate one deployment's accepted risk to every context binding it. Exceptions are per-command and per-flag, because a team that wants one experimental command should not have to grant the entire experimental surface.

The default floor is experimental, so humans and interactive agents keep getting new (badged) surface; only pinned contexts see the narrower set. A refused call reports stability_blocked or deprecated_surface in agent mode, now listed alongside safety_blocked and profile_blocked where a consumer looks for them (#546).

And the policy behind the badge is written down (#548): the tier carries the contract, not the version number. A stable command is additive-only from the release that declared it, whatever the version does next — 0.x permitting a break is not a licence to take one.

dtctl inventory arrivals — is data arriving right now? (#454)

dtctl inventory answers "what data exists here", but its verdicts are retention-scoped: a stream that received data once last week reads as present. That makes it useless for the question you actually have after instrumenting a service or pointing a collector at a tenant.

dtctl inventory arrivals --since 15m --scope 'service.name == "checkout"'
dtctl inventory arrivals --scope 'service.name == "checkout"' --require logs,spans

It reports a per-signal state — live, stale, empty, no-data, n/a, absent, or unknown — with counts and last-seen. Two of those only become expressible with a window, and they are the diagnostic ones: stale (the source emitted during rollout, then stopped) and empty (the stream holds data within retention, but this scope is not producing into it).

--require turns it into a pipeline gate: exit 10 if a required signal is not live, exit 11 if there is no verdict. unknown is never reported as absence — on a high-volume tenant a truncated probe says so rather than guessing.

Per-context query limits (#505)

A cap you have to remember to type is not a cap. --default-scan-limit-gbytes and friends had to be passed on every invocation, so they protected exactly the queries someone thought to protect — not the generated ones, the scripted ones, or the agent-driven ones, which are the queries most likely to scan something unintended.

query-limits:                 # global defaults
  scan-limit-gbytes: 500
  max-result-records: 5000

contexts:
  - name: production
    context:
      query-limits:
        scan-limit-gbytes: 50   # tighter here; other limits inherited

Precedence is flag → context config → global config → server default, and the per-context block overrides the global one per field, so a context can tighten one limit without restating the others. A config without the block behaves exactly as before.

Non-interactive OAuth login (#448)

auth login used the authorization code flow, which needs a browser and a human to consent. CI runners have neither, so pipelines fell back to a long-lived platform token or re-implemented the token request in shell.

Supplying a client ID and secret now switches auth login to the client credentials grant (RFC 6749 §4.4) — no browser, no user. Prefer the environment variables over flags, since argv is visible to other processes:

DTCTL_CLIENT_ID=... DTCTL_CLIENT_SECRET=... DTCTL_ACCOUNT_URN=... dtctl auth login

Supplying only one half of the pair is a usage error rather than a silent fallback to the browser flow.

New resources and commands

  • Platform management (#425) — read-only get/describe for environment, license, and license-settings (/platform/management/v1).
  • scheduling-rule (#445) — a native resource for maintenance and alerting schedules.
  • update extension (#453) — activate a specific Extensions 2.0 version as the environment-wide active one.
  • Central cloud enrichment (#442) — for AWS, Azure, and GCP monitoring configs. The useIngestEnrichmentConfig and ingestEnrichmentMigrationProcessed fields are modificationPolicy: NEVER and nullable in the live activation schemas, which is the load-bearing detail: a PUT that omits one is itself a modification, so both now survive a read-modify-write verbatim.
  • update document --create-snapshot (#452) — opt-in, since updates otherwise overwrite content in place.

Service mode hardening (#470, #472, #473)

pkg/engine gained admission control with a bounded queue, request-scoped context propagation and cancellation, and output caps that surface Truncated rather than streaming unbounded bytes. dtctl serve http gained ReadTimeout, WriteTimeout, and IdleTimeout, and long-running streams (--watch, --follow) are gated behind a capability flag so they cannot be reached in service mode.

⚠️ Breaking changes

Three, all in output contracts that affect agents and scripts rather than interactive use.

--jq filters that resolve to null now fail (#482, fixes #413). jq answers a missing key with null, and dtctl returned that null with exit code 0 and nothing on stderr — so a filter written for the wrong shape was indistinguishable from a genuinely empty result, which is exactly the distinction automation needs. In Grail a wrong filter already yields [] rather than an error, so the CLI was the only layer that could preserve "no data" vs "you asked wrongly", and it removed it instead. A null-resolving filter now exits 1 with error code jq_shape_mismatch, naming the keys its input actually carries. A filter that selected nothing (empty, a non-matching select(...)) stays a genuine empty answer and returns []. On query, --agent --jq emitted {result, metadata} and now emits the full {ok, result, context, metadata} envelope.
→ If you treated null as "no data", test for [] instead.

query -A -o toon keeps the agent envelope (#455). Below the spill threshold this emitted a bare TOON document — no ok, no context, so the heavy-scan warnings and suggestions were silently lost — while above the threshold spill re-imposed a JSON envelope. Same flags, two contracts depending on how much data came back. Rows now stay inside the envelope as a TOON string under result.records, with encoding: "toon" naming the codec. The envelope costs ~3–4% more tokens than the bare document, in exchange for ok, error.code branching, the scan warnings, and a contract that no longer changes with result size.
→ Read result.records instead of parsing stdout as TOON.

Local configs and session credentials are sealed (#468). An auto-discovered .dtctl.yaml can no longer expand ${VAR} references, carry an inline token, or name a non-Dynatrace host, and it can no longer reach stored OAuth tokens — closing a path where a rogue config in a working directory could redirect them. Session credentials are sealed to their inline token so embedded sessions cannot fall back to host credential stores.
→ Point DTCTL_CONFIG at a trusted config file, where variable expansion and inline tokens remain supported.

Relatedly, agent mode used to downgrade -o csv/yaml/table to JSON silently; it still falls back, but now names the requested format in context.warnings.

Not a breaking change, despite the generated changelog: the inventory entry refers to --since / --where becoming arrivals --scope. Those flags never existed on inventory in a released build — they were renamed while #454 was still in development. Upgrading from 0.38.0 requires nothing.

🔒 Security

  • Resource ids are escaped in URL paths (#545, fixes #492) — every API handler interpolated an id into the request path with fmt.Sprintf and no escaping, so an id carrying a URL-significant character addressed a different resource and the request succeeded against it. The worst case is exactly the one you would not want: dtctl delete slo "<id>#x" -y reported OK SLO deleted with exit 0, because net/http drops the fragment before the request goes out — the delete was performed on <id>. ? truncated ids the same way.
  • Supply chain (#530, #532) — every GitHub Action is now pinned by commit SHA and maintained by dependabot, and all workflows run with an explicit ...
Read more

v0.38.0

Choose a tag to compare

@github-actions github-actions released this 14 Aug 06:42
b10e188

This release makes dtctl embeddable — pkg/engine runs full dtctl command lines in-process with byte-identical output, and v0.38.0 is the first release you can actually go get — and adds API spec discovery plus a governed HTTP passthrough for the platform APIs dtctl doesn't wrap natively.

✨ Highlights

API spec discovery + a governed HTTP passthrough (#420)

dtctl wraps ~20 platform APIs natively; an environment publishes many more. Reaching an unwrapped API used to mean ad-hoc JavaScript through exec function — the least governed path in the tool was also the most convenient one. Now:

dtctl get apis [--uncovered] [--ops-count]        # what the environment actually publishes
dtctl describe api <name> [--operation 'METHOD /path'] [--raw]
dtctl exec api <path> [-X METHOD] [-d BODY|@file|@-] [-H 'Name: value'] [--dry-run]
  • The listing mirrors the environment's own API index. The DTCTL column names the native command that already covers an API, so --uncovered turns the platform-vs-dtctl gap into a contribution backlog.
  • describe api projects rather than dumps: published specifications run to tens of thousands of tokens, so the default view is the operation index — every operation as METHOD /path with its summary and declared scope. --operation drills into one operation in full (parameters, body schema, responses, scopes, a ready-to-run invocation).
  • The exec api safety verdict is resolved from the API's own specification, never from the HTTP method: a sweep of one production index found 15 POST operations that declare only a read scope (verification and search endpoints that are POSTs purely because they take a body), while a POST elsewhere deletes stored records. The verdict is the stricter of two independent floors — what the method guarantees and what the specification declares — an unresolvable request is gated as a delete, and there is deliberately no flag to assert otherwise.
  • The passthrough is built to stay an escape hatch: unadvertised (visible only in dtctl commands --full), granted by no command profile, and it names the native command whenever one covers the path.

Also fixed along the way: every exec subcommand now gates on the safety level — a readonly context previously did not block dtctl exec function --code '…'.

dtctl is now embeddable (#417, #419)

cmd.Run and pkg/engine run one dtctl command line per request in-process — multi-tenant sessions, per-request virtual files, zero host capabilities — with a guard test asserting byte-identical stdout/stderr/exit code between the engine and the CLI. dtctl serve http ships as an experimental reference server on top, registered only when DTCTL_EXPERIMENTAL_SERVE is set.

And it is actually consumable (#419): the root module's sdk require had named a placeholder pseudo-version since the module's first day, so go get github.com/dynatrace-oss/dtctl@<any tag> failed with unknown revision — in-repo builds resolved the sdk through a local replace that Go ignores downstream, so nothing ever validated the require. It now pins a real, release-please-managed version, every release mirrors its tag into sdk/vX.Y.Z, and a verify-consumable release job proves the import from a module outside the repo. v0.38.0 is the first importable release, and SDK versions are now aligned with the CLI's (the sdk tag jumps 0.2.0 → 0.38.0).

Two intended CLI-visible behavior changes ride along:

  • apply --write-id now actually rewrites the source file. It was a silent no-op unless apply hooks happened to be configured — which is not what the flag documented.
  • apply prints a new stderr hint on every create-via-apply without --write-id, suggesting how to make future runs update instead of create.

🔒 Security

Release binaries are now built with Go 1.26.6 (#421), clearing seven govulncheck findings in standard-library code paths dtctl actually calls (net/http, encoding/xml, encoding/asn1, x/net idna).

🐛 Fixes

  • create extension always failed with HTTP 415 (#348) — the upload was sent as multipart/form-data, but the Extensions v2 API requires the raw ZIP bytes with Content-Type: application/octet-stream. Extension uploads work now.
  • DQL quotes silently lost on Windows PowerShell (#418) — Windows PowerShell 5.1 strips embedded double quotes from arguments to native programs, turning filter loglevel == "INFO" into a field-to-field comparison that is valid DQL and quietly returns zero rows (reproduced: 0 records, no warning, 13 GB scanned). dtctl now re-reads its raw command line on Windows, detects the mangling, and warns with the forms that work; --file - with stdin on a terminal fails fast instead of hanging; and the docs no longer recommend a here-string form that went through the same mangling. PowerShell 7.3+ was never affected.

Full Changelog: v0.37.0...v0.38.0

v0.37.0

Choose a tag to compare

@github-actions github-actions released this 07 Aug 12:55
ad5e36d

This release rounds out the Live Debugger command surface with log-message support and a dedicated snapshots command, introduces a translate verb for OpenPipeline expression work, and closes several data-correctness bugs.

✨ Highlights

Live Debugger: log messages (#396)

Breakpoint log messages — the line emitted each time a breakpoint is hit — are now fully first-class.

  • update breakpoint --log-message — set or change the log message template ({variable} placeholders supported). Omitting the flag leaves the current message untouched; at least one of --condition, --log-message, or --enabled is required.
  • get breakpoints — new LOG MESSAGE column in the default table.
  • describe breakpoint — shows the message in both text and structured output.

Display strips the qualified-store prefixes the backend adds (store.rookout.frame.line → {frame.line}, store.rookout.variables.x → {x}), so what you type is what you see.

Live Debugger: surface rule ID + get snapshots (#386)

Two long-standing friction points in the breakpoint → snapshot workflow, fixed together.

create breakpoint now prints the stable rule ID on success:

Created breakpoint at OrderController.java:306 (id: dtctl-rule-5bfb45a29fce7a46)

dtctl get snapshots <breakpoint> is a new command that fetches snapshots for a given breakpoint without needing to know (or look up) the backend-assigned immutableId. Accepts a breakpoint by filename:line or stable rule ID, resolves the immutableId internally, and runs the DQL query for you. Supports --decode-snapshots, -o json/yaml, timeframe flags, and --max-result-records.

translate verb (#378)

A new translate verb groups OpenPipeline translation commands under a consistent noun.

dtctl translate lql-to-dql 'log.source="x"'    # translate an LQL matcher to DQL
dtctl translate classic-pipelines logs          # export a classic pipeline as JSON

translate lql-to-dql accepts a positional expression or -f file; default output is the raw DQL string (pipeable), -o json/-o yaml/-o table give structured output.

Note: dtctl get classic-pipelines-translation is removed — use dtctl translate classic-pipelines instead. Also fixes the OAuth scope for the classic-pipelines endpoint (settings:objects:read, not openpipeline:configurations:read).

🐛 Fixes

  • Panic on apply with nested content (#404) — apply -f on a document whose body contains its own content key (reachable via the get -o json → edit → apply round-trip) triggered a bare type-assertion panic before the safety check ran, producing a raw Go stack trace instead of an error. Now matches the guarded path already present in create document.
  • unshare --all left shares behind (#405) — ListDirectShares issued a single unpaginated GET, so on a document with more than one page of direct shares the command deleted page one, reported success, and left the rest in place. Every subsequent page is now fetched before the delete pass.
  • Account token leaked into plugin environment (#403) — DTCTL_ACCOUNT_TOKEN was not stripped when dispatching to exec plugins. pluginEnv already stripped DTCTL_TOKEN and DT_API_TOKEN; this was a missed update after DTCTL_ACCOUNT_TOKEN landed in a later PR. Plugins continue to promise "never tokens" as documented.

Full Changelog: v0.36.0...v0.37.0