00Plan

Plan: Add per-service default actions so awsquery ec2 runs without an action

Issue: ty5dx-add-per-service-default-actions-so-awsquery-ec2-runs-without-an-action · Generated 2026-09-08


01Strategy

Strategy summary

Resolve the default action before argparse sees the tokens, not after. One pure list → list function spliced into the raw argv at cli.py:752, so every downstream stage sees exactly the argv the user would have typed.

The issue's own framing is wrong — and fails silently. Both positionals are nargs="?", so argparse eats the -- separator and promotes the first column filter into the action slot. ec2 -- InstanceId parses byte-identically to ec2 InstanceId. A post-parse patch keyed on args.action is None therefore leaves awsquery ec2 -- InstanceId running a bogus operation named InstanceId. The information exists only in the raw token list.
Kebab-case in the YAML is a correctness rule, not a style choice. is_readonly_operation only PascalCases input that contains a hyphen. Given describe_instances it returns false, falls through to the unsafe-operation prompt and blocks on input() — awsquery ec2 would hang in a pipe. The decision document's illustrative snake_case example is overridden.
Equivalence by construction, not by parallel code paths. Because injection happens pre-parse, the filter builder, the two-pass reorder, the multi-level filter parser, readonly validation and default columns all need no change. The research replayed the real pipeline over 11 bare/explicit command pairs: all equivalent.
A separate config file leaves every existing walker untouched. Two walkers assume the strict service → action → {columns} shape; a sibling string key inside default_filters.yaml would raise AttributeError in both. Hence a new src/awsquery/default_actions.yaml — which must also be registered in the package-data list, or the wheel ships with no defaults at all.
Six curated picks are editorially arguable and surfaced for veto. All 50 entries pass four mechanical gates. The question "which operation is the default" is a judgement call for iam, ssm, sagemaker, glue, backup and athena. Each is a one-line YAML change.
50curated services
5services deliberately skipped
6picks needing sign-off
6implementation tasks
Unverified baseline

The research agent could not run the test suite — its interpreter lacked boto3, botocore, argcomplete and pytest. The executor must actually run make test, make lint and make format-check rather than assume the suite is green.


02Flow

Task flow & dependencies

Task 1 Curated default_actions.yaml + packaging 50 verified kebab-case entries; register in pyproject package data.
Task 2 config.py loader and accessor load_default_actions() / get_default_action(), mirroring the filters pair.
Task 3 Pre-parse argv injection + stderr fallback The load-bearing change: inject at cli.py:752, split the dead-end branch.
Task 4 Map validation, injector and flag tests Four existing unit files extended; no new test file.
Task 5 Rewrite the vacuously-green e2e test test_end_to_end.py:889 would pass while attempting real AWS work.
Task 6 README documentation Feature bullet, config subsection, additive-columns row.

Tasks run sequentially; task N may assume 1..N−1 are complete. Tasks 1 and 2 are the data/loader foundation, task 3 is the behavioural change, tasks 4–6 verify and document it.


03Decisions

Key decisions & options

Where the default is resolved Chosen: pre-parse argv injection

Chosen: a pure _inject_default_action(argv) called on sys.argv[1:] at cli.py:752, after argcomplete.autocomplete() and without ever mutating sys.argv.

Rejected — post-parse substitution on args.action is None (what the issue text implies): provably insufficient, since argparse makes ec2 -- InstanceId and ec2 InstanceId indistinguishable after parsing. It fails an acceptance criterion silently.

Rejected — look the second positional up in botocore and demote non-operations to value filters (which would also make awsquery ec2 prod default): a typo such as describe-instance would then silently run describe-instances with a spurious filter and print zero rows instead of erroring, and it adds a service-model load to the hot path.

Canonical spelling of the YAML values Chosen: kebab-case, mandatory

Snake_case values were executed against the real readonly validator and returned is_readonly=False for every entry, routing the command into a blocking interactive prompt. Kebab-case in the file, plus a defensive to_kebab_case(to_snake_case(v)) at the injection boundary, and a test that pins the file as already canonical.

Config lives in its own file Chosen: src/awsquery/default_actions.yaml

A default_action: key inside default_filters.yaml would break the audit script and two existing structure tests, which walk every service value as a dict. The separate file leaves them untouched — but it must be added to the package-data list in the same commit, since there is no MANIFEST.in and the unit tests read from the source tree and cannot catch a missing wheel entry.

iam default: list-users vs list-roles Open — maintainer veto invited

The highest-disagreement curated pick. list-users chosen because Users is the console landing page and IAM's primary noun; list-roles is the more infra-centric answer and a defensible override. Five further judgement calls (ssm, sagemaker, glue, backup, athena) are tabulated in the plan. Every one is a one-line change.

Services with no parameterless entry point Note: 5 deliberately skipped

application-autoscaling, budgets, ce, cognito-identity and cognito-idp have zero readonly operations with no required parameters, so the locked "single immediate call" rule excludes them. They intentionally exercise the stderr fallback, which is what the rewritten end-to-end test asserts against.

The end-to-end test that would pass while broken Risk: vacuous green

tests/integration/test_end_to_end.py:889 guards its assertions behind a string this change removes. Left alone it stays green while main() proceeds to attempt real AWS work under a partially-mocked session — a CI hang or credential error that looks like a passing test. Task 5 rewrites it into two unconditional assertions.


04Detail

Full detail

Show full artifact source click to expand

05Provenance

Provenance

Generated from the canonical .md. This HTML is never hand-edited — if the .md changes, regenerate with /issue:view.