Repository navigation
v0.40.0
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
metadatablock 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-tokensbound the whole inline result, keeping the leading rows that fit.--series=downsample:Nkeeps up to N extreme-preserving points.get --limitand--fieldswork 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.
querychecks a DQL statement's storage scopes before sending it, and fails withinsufficient_scope, naming the missing scopes and what the token can read. Agents no longer fan out into the sameNOT_AUTHORIZED_FOR_TABLEover 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) ormetric_not_in_window(the metric key has no data in the window) incontext.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:
result.recordscan be a string. Under-o autoit holds CSV or YAML text, andcontext.formatnames it. This is the sharpest edge. → Pin-o json, or branch oncontext.format.- Rows lose constant columns and nulls. A column that is constant across all rows now lives in
result.constant. → Mergeresult.constantinto each row, or pass--compact=false. - Numbers are rounded and series are summarized. → Pass
--series=full --precision 0to recompute from raw values. getlists stop at 50 items. → Checkcontext.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.
deleteandrestorenow implement it: they resolve the target, print what they would do, and send no mutating request.exechas no dry run, exceptexec api.- A dry run is gated like a read by the safety checks, so previews work in
readonlycontexts.
→ If a script passed--dry-runto 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, sodescribe segment "<id>#x"showed a different segment than the one named. It also left:unescaped, sodescribe analyzer "foo:poll"polled the analyzerfoo. 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.txtis now signed with cosign v3 intochecksums.txt.sigstore.json, replacingchecksums.txt.sig. Verify withcosign 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"withCTXunset 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 dqland the rest read a file literally named-. If one existed they applied it and reported success. Every--fileinput 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
FAILEDstate from a synchronous execute, orCANCELLED/RESULT_GONEfrom 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 asunknown_query_state. create gcp monitoringbroke Smartscape discovery (#604, fixes #573) — without--locationFilteringit 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 allrepairs configs created by earlier versions. The loss oflogsConfigurationon update/apply is tracked in #608.logs workflow-execution --tasks/--allhid 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_unavailablein 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-typeswas dropped in agent mode (#605, fixes #433) — the types block now comes back asresult.typesin inline, spilled and budget-cut results.-o toonfailed 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 returnsunknown_commandwith a runnable suggestion (dtctl get dashboards), and query truncation notices no longer go to stderr when the envelope already carries them. dtctl commandscatalog 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 likeenable aws monitoringare markedmutating: 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.mdanddocs/AGENT_MODE.md(#603). docs/AGENT_MODE.mddocuments 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