Skip to content

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 --task <name> retry suggestion for each failed task. A 404 on one task doesn't count as a failure while others were fetched. Under --follow, a failed fetch prints a warning and streaming continues; only the final fetch decides the exit code.
  • query --include-types was dropped in agent mode (#605, fixes #433) — the types block now comes back as result.types in inline, spilled and budget-cut results.
  • -o toon failed on control characters (#588) — a single ESC from an ANSI color code in a log line failed the whole output. Control characters are now shown as Unicode Control Pictures (␛).
  • Agent-mode parse errors under auto-detection (#592) — unknown flags and commands came out as prose when agent mode was detected from the environment rather than -A. They are now enveloped. An unknown resource type returns unknown_command with a runnable suggestion (dtctl get dashboards), and query truncation notices no longer go to stderr when the envelope already carries them.
  • dtctl commands catalog below the verb level (#562) — per-resource flags are listed (resource_flags), nesting is no longer cut off at the third level, nested mutating commands like enable aws monitoring are marked mutating: true, and help topics are no longer listed as read commands.

📚 Documentation

  • The stability policy for agent-mode output is documented in AGENTS.md, docs/STABILITY.md and docs/AGENT_MODE.md (#603).
  • docs/AGENT_MODE.md documents the exit codes (#558) and every new agent default with its opt-out.
  • The docs say clearly that dtctl targets Dynatrace SaaS (#565), and the macOS global config path is fixed (#575).

Full Changelog: v0.39.0...v0.40.0