Skip to content

Add several composite CLI commands for common multi-step workflows - #10894

Draft
PCManticore wants to merge 4 commits into
aws:v2from
PCManticore:barisk/high-order-commands
Draft

PCManticore wants to merge 4 commits into
aws:v2from
PCManticore:barisk/high-order-commands

Conversation

@PCManticore

Copy link
Copy Markdown

Why

The AWS CLI is the primary interface agents use to interact with AWS. Agents chain 5-12+ raw API calls to perform one logical operation (run a query, diagnose a service, investigate an alarm etc) and routinely drop steps, mishandle polling, produce unbounded output, or lose context between calls.

Four workflows stand out:

Workflow Calls chained What goes wrong
Run an Athena query StartQueryExecution → poll GetQueryExecution → paginate GetQueryResults → parse → type-cast Polling errors, missed pagination, raw ResultSet structure unparsed, no timeout handling
Query CloudWatch Logs Insights DescribeLogGroups → StartQuery → poll GetQueryResults → StopQuery on timeout Log group resolution failures, no window splitting for large results, dangling queries on timeout
Investigate a CloudWatch alarm DescribeAlarms → resolve composite children → extract metric config → GetMetricStatistics per metric Composite alarm rule parsing, dimension extraction, time window calculation, unbounded metric fan-out
Diagnose an ECS service DescribeServices → DescribeTaskDefinition → ListTasks → DescribeTasks → log retrieval → target health 6-12 calls with branching error paths, secret leakage in log output, partial failure handling

These commands address this by collapsing each workflow into a single CLI call.

What

Four new CLI customization commands, plus a shared compositereadonly/ support package:

aws athena run-query — Submits a SQL query to Athena, polls for completion, paginates results, and returns structured JSON with type-cast values and execution metadata. Handles workgroup output location resolution, configurable polling with timeout and auto-cancellation, and row capping.

aws logs run-insights-query — Runs a Logs Insights query across one or more log groups. Resolves log group names, prefixes, and ARNs; polls with auto-cancellation; splits large time windows when the result set exceeds the service limit; deduplicates across sub-windows; and caps output rows.

aws cloudwatch alarm-metric-sweep — Resolves an alarm (including composite alarm children) to its underlying metrics, calculates the appropriate time window, and pulls bounded metric data for each. Returns a correlated view of alarm state and metric data in one call.

aws ecs diagnose-service — Gathers a full diagnostic view of an ECS service: configuration, task definitions, running and stopped tasks, recent service events, log evidence, and load balancer target health. Output is bounded and secrets in log evidence are redacted.

All commands follow the same contract: errors are returned as data (never raised), every result is a structured JSON envelope, every bound and adjustment is reported in a guidance array, and exit codes are machine-parseable.

Developer experience

aws athena run-query

Before (5-7 steps)
QUERY_ID=$(aws athena start-query-execution \
  --query-string "SELECT * FROM my_table LIMIT 10" \
  --result-configuration OutputLocation=s3://my-bucket/results/ \
  --query 'QueryExecutionId' --output text)

while true; do
  STATUS=$(aws athena get-query-execution --query-execution-id "$QUERY_ID" \
    --query 'QueryExecution.Status.State' --output text)
  [ "$STATUS" = "SUCCEEDED" ] && break
  [ "$STATUS" = "FAILED" ] && { echo "Failed"; exit 1; }
  sleep 2
done

aws athena get-query-results --query-execution-id "$QUERY_ID" > results.json
NEXT=$(jq -r '.NextToken // empty' results.json)
while [ -n "$NEXT" ]; do
  aws athena get-query-results --query-execution-id "$QUERY_ID" \
    --next-token "$NEXT" > page.json
  NEXT=$(jq -r '.NextToken // empty' page.json)
done
jq -r '.ResultSet.Rows[1:][] | [.Data[].VarCharValue] | @tsv' results.json
After (1 call)
aws athena run-query \
  --sql "SELECT * FROM my_table LIMIT 10" \
  --catalog AwsDataCatalog \
  --database default \
  --output-location s3://my-bucket/results/

aws logs run-insights-query

Before (4-6 steps)

aws logs describe-log-groups --log-group-name-prefix '/aws/lambda/' \
  --query 'logGroups[].logGroupName' --output text

QUERY_ID=$(aws logs start-query \
  --log-group-names '/aws/lambda/MyFunc' \
  --start-time "$(date -d '1 hour ago' +%s)" \
  --end-time "$(date +%s)" \
  --query-string 'fields @timestamp, @message | limit 100' \
  --query 'queryId' --output text)

while true; do
  RESULT=$(aws logs get-query-results --query-id "$QUERY_ID")
  STATUS=$(echo "$RESULT" | jq -r '.status')
  [ "$STATUS" = "Complete" ] && break
  [ "$STATUS" = "Failed" ] && { echo "Failed"; exit 1; }
  sleep 1
done

echo "$RESULT" | jq -r '.results[] | map(.value) | @tsv'
# If too many results: split time window, merge, dedup
# On timeout: aws logs stop-query --query-id "$QUERY_ID"

After (1 call)

aws logs run-insights-query \
  --log-group-names '/aws/lambda/MyFunc' \
  --query-string 'fields @timestamp, @message | sort @timestamp desc | limit 100' \
  --start-time 2024-01-15T00:00:00Z \
  --end-time 2024-01-15T23:59:59Z

aws cloudwatch alarm-metric-sweep

Before (4-10+ steps)

ALARM=$(aws cloudwatch describe-alarms --alarm-names 'MyAlarm')

# If composite: parse AlarmRule, extract ALARM("child") references, re-describe each
RULE=$(echo "$ALARM" | jq -r '.CompositeAlarms[0].AlarmRule // empty')
# ... recursively resolve children ...

NS=$(echo "$ALARM" | jq -r '.MetricAlarms[0].Namespace')
MN=$(echo "$ALARM" | jq -r '.MetricAlarms[0].MetricName')
PERIOD=$(echo "$ALARM" | jq -r '.MetricAlarms[0].Period')
STAT=$(echo "$ALARM" | jq -r '.MetricAlarms[0].Statistic')
DIMS=$(echo "$ALARM" | jq -r '.MetricAlarms[0].Dimensions
  | map("Name=\(.Name),Value=\(.Value)") | join(" ")')

START=$(date -u -d "@$(( $(date +%s) - PERIOD * 12 ))" +%Y-%m-%dT%H:%M:%SZ)
END=$(date -u +%Y-%m-%dT%H:%M:%SZ)

aws cloudwatch get-metric-statistics \
  --namespace "$NS" --metric-name "$MN" \
  --dimensions $DIMS \
  --start-time "$START" --end-time "$END" \
  --period "$PERIOD" --statistics "$STAT"

# Repeat for each child metric of a composite alarm
# Paginate if many datapoints
# Assemble the full picture

After (1 call)

aws cloudwatch alarm-metric-sweep --alarm-name 'MyAlarm'

aws ecs diagnose-service

Before (6-12 steps)

aws ecs describe-services --cluster my-cluster --services my-service > svc.json

TASK_DEF=$(jq -r '.services[0].taskDefinition' svc.json)
aws ecs describe-task-definition --task-definition "$TASK_DEF" > taskdef.json

aws ecs list-tasks --cluster my-cluster --service-name my-service \
  --query 'taskArns' --output json > taskarns.json

aws ecs describe-tasks --cluster my-cluster \
  --tasks $(jq -r '.[]' taskarns.json) > tasks.json

jq -r '.services[0].events[:10][] | "\(.createdAt)  \(.message)"' svc.json

CI=$(jq -r '[.[].containerInstanceArn] | join(" ")' tasks.json)
aws ecs describe-container-instances --cluster my-cluster \
  --container-instances $CI

# Extract log group from task def, build stream name per task
aws logs filter-log-events --log-group-name "$LOG_GROUP" \
  --log-stream-names "$STREAM" --limit 50

TG=$(jq -r '.services[0].loadBalancers[0].targetGroupArn // empty' svc.json)
aws elbv2 describe-target-health --target-group-arn "$TG"

# Correlate findings, handle partial failures, redact secrets from logs

After (1 call)

aws ecs diagnose-service --service my-service --cluster my-cluster

Summary

Before After
Athena 5-7 calls + poll loop + pagination loop + result parsing 1 call
Logs 4-6 calls + poll loop + time-window splitting + dedup + cleanup 1 call
CloudWatch 4-10+ calls + composite resolution + per-metric fan-out 1 call
ECS 6-12 calls + batching + log wiring + secret scrubbing 1 call
Error handling DIY at every step Structured JSON with code, guidance, exit codes
Timeout DIY (and usually forgotten — leaves queries running) Auto-cancel built in
Output Raw API responses, manual parsing Structured JSON, typed values, bounded

Key behaviors:

Behavior Detail
Bounded output Every command caps its output (default ~50 rows for Logs, configurable via --max-rows; series and datapoint caps for CloudWatch). Truncation is always reported in guidance.
Errors as data Failed operations return a JSON result envelope with error, code, and guidance — never an unhandled exception. Callers branch on exit code and code field.
Exit code contract 0 = success, 252 = input validation error (fix the arguments), 255 = operational error (resource not found, service error, timeout).
Client-side validation Parameters are validated before any API call. Invalid inputs return exit 252 with a message naming the problem and the fix.
Polling with timeout Athena and Logs commands poll with configurable interval and max wait. On timeout, running queries are cancelled automatically.
Structured guidance Every adjustment, truncation, and recovery hint is surfaced in a guidance array in the output. The caller never has to guess what happened.
Secret redaction ECS log evidence is scrubbed for AWS access keys, credential-like key-value pairs, and common secret patterns before being included in output.
Composite alarm resolution CloudWatch resolves ALARM() references in composite alarm rules, walking the alarm tree to find the underlying metrics.
Log group resolution Logs accepts exact names, prefixes with *, and ARNs. Resolution results are included in the output so the caller can see what matched.
--output table and --output text Both AWS CLI output formats work. Table mode renders nested sections; text mode uses tab-delimited rows with section prefixes.

Disclosure: This code was generated with AI assistance and reviewed by the authors.

Claudiu Popa and others added 4 commits November 12, 2026 15:37
Generated by AI tools, and reviewed by Claudiu Popa <clpo@amazon.com>

Co-authored-by: Baris Kurt <barisk@amazon.com>
Generated by AI tools, and reviewed by Claudiu Popa <clpo@amazon.com>

Co-authored-by: Baris Kurt <barisk@amazon.com>
Generated by AI tools, and reviewed by Claudiu Popa <clpo@amazon.com>

Co-authored-by: Baris Kurt <barisk@amazon.com>
Generated by AI tools, and reviewed by Claudiu Popa <clpo@amazon.com>

Co-authored-by: Baris Kurt <barisk@amazon.com>
@ashovlin ashovlin self-assigned this Nov 13, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants