Cookbook¶
Common recipes for daily aws-tui use. Each recipe is end-to-end — commands you can copy/paste plus the in-app key sequence.
- Connect to and switch between data sources
- Switch the theme on the fly
- Customize a keybinding
- Diagnose an interrupted transfer after a crash
- Browse AWS Glue safely
- Run Athena queries safely
- Inspect and query Glue tables through Athena
1. Connect to and switch between data sources¶
Walks through three setups people hit on day one:
- §1.1–§1.5 — connect to local Adobe S3Mock from scratch (the canonical "first s3-compatible endpoint" walkthrough).
- §1.6 — jump between AWS profiles with one keystroke (multi-account flows).
- §1.7 — run several
s3-compatibleendpoints side-by-side.
You have S3Mock running on http://localhost:9000 with arbitrary test
credentials test / test. The shipped harness creates the canonical
dev-s3 connection; the manual examples below use s3mock-local
to show that connection names are user-defined.
1.1. Start S3Mock (skip if already running)¶
Quickest path — dev seeded S3Mock (recommended for first-time exploration; ships ~5 buckets and ~90 objects so you have content to navigate):
scripts/test-services/s3/up.sh
This wraps docker compose + seed.py and prints the path to
scripts/test-services/s3/config-snippet.toml; add that snippet to
<config-dir>/config.toml to create dev-s3. Teardown is
scripts/test-services/s3/down.sh (add --purge to wipe the data
volume). See scripts/test-services/README.md for the seeded
dataset and how to extend it.
Plain S3Mock (no seed):
docker run --rm -d --name s3mock \
-p 127.0.0.1:9000:9090 \
adobe/s3mock:5.2.2@sha256:e7c36014dcf4c7f0f6bec9de888477d1d9b8f55eceb7d9c1186f70d7aadf81ca
1.2. Store the credentials in the macOS Keychain (recommended)¶
The resolver expects two required keychain entries under ONE service name
(matching the credentials = "keychain:<service>" value in
config.toml): one account named access_key_id and one named
secret_access_key. Temporary credentials may also provide an
optional session_token account. So for a keychain:s3mock-local
config entry:
# service="s3mock-local", account="access_key_id"
security add-generic-password \
-s s3mock-local -a access_key_id -w test
# service="s3mock-local", account="secret_access_key"
security add-generic-password \
-s s3mock-local -a secret_access_key -w test
# optional, only for temporary credentials
security add-generic-password \
-s s3mock-local -a session_token -w '<session-token>'
(The Python keyring library aws-tui uses delegates to the macOS
Keychain by default.)
1.3. Add via the in-TUI Settings form¶
Open Settings with ,, add an S3-compatible connection, and fill the
form:
| Field | Value |
|---|---|
| Name | s3mock-local |
| Endpoint URL | http://localhost:9000 |
| Region | us-east-1 |
| Access key ID | test |
| Secret access key | test |
| Session token | Optional; only for temporary credentials |
The form stores the secret fields in the OS keychain through keyring and
persists only a keychain: reference in config.toml. New saves use the
URL-escaped aws-tui:connections/<url-escaped-name> namespace; later edits
alternate between the aws-tui:connection-revisions/<url-escaped-name>/0 and
/1 services. The resulting entry is equivalent to the following shape:
[connections.s3mock-local]
kind = "s3-compatible"
endpoint_url = "http://localhost:9000"
credentials = "keychain:aws-tui:connections/s3mock-local"
force_path_style = true
verify_tls = false # http:// S3Mock -> no cert to verify
1.4. Add by editing the file directly¶
If you already have other connections, just append:
[connections.s3mock-local]
kind = "s3-compatible"
endpoint_url = "http://localhost:9000"
credentials = "static" # tells the resolver to use inline keys below
access_key_id = "test"
secret_access_key = "test"
force_path_style = true
verify_tls = false # http:// S3Mock -> no cert to verify
For multiple S3-compatible services, just add more
[connections.<name>] blocks — the <name> (e.g. s3mock-local,
r2-prod, b2-archive) becomes the source identifier shown in
the pane's bottom border (s3-compatible · s3mock-local · localhost:9000).
Region is optional and intentionally not displayed for
s3-compatible connections — MinIO/R2/B2/etc. don't have a
meaningful region, so the pane title shows name · endpoint
instead.
1.5. Use it¶
aws-tui
Then in-app, press Shift+S on the focused pane to cycle
through every available source — local → every TOML /
auto-discovered connection → wrap. AWS profiles auto-discovered
from ~/.aws/credentials show up as
aws s3 · {profile} · {region}; TOML s3-compatible entries
show up as s3-compatible · {name} · {endpoint}. Tap
Shift+S until the pane title reads
s3-compatible · s3mock-local · {endpoint} — the bucket list
should populate immediately.
:opens the command palette. ItsSwitch sourcecommand invokesapp.swap_sourceand cycles resolver order;Shift+Sis the one-keystroke equivalent. Neither path selects one exact source.
1.6. Jump between AWS profiles with one keystroke¶
If you have several [profile *] blocks in ~/.aws/config (typical
for orgs with multiple AWS accounts or SSO permission sets), Shift+S
is the fastest way to flip between them. Each press re-mounts the
focused pane on the next profile in the cycle; the pane's bottom
border subtitle (aws s3 · {profile} · {region}) tells you which
identity you're on.
~/.aws/config:
[profile dev]
region = us-east-1
sso_session = my-org
[profile staging]
region = us-east-1
sso_session = my-org
[profile prod]
region = us-west-2
sso_session = my-org
In-app:
- Shift+S → left pane re-mounts on
aws s3 · dev · us-east-1. - Shift+S →
aws s3 · staging · us-east-1. - Shift+S →
aws s3 · prod · us-west-2.
Per-pane independence: put dev on the left and prod on the right,
then c to copy an object between them — CrossFsCopy streams S3→S3
without an intermediate local hop.
The cycle also includes any s3-compatible entries from
config.toml and the local filesystem. Add MinIO / R2 / B2 / Wasabi
connections via the in-app Settings nav page (,) — they join
the cycle immediately, no relaunch.
Expired SSO tokens are detected offline at launch via the SSO cache freshness probe (see connections.md §3); expired or missing SSO profiles are skipped by the boot chain, marked unreachable for the session, and surfaced through a recovery toast while the app mounts local panes instead of hanging. Run
aws sso login --profile <name>and relaunch, or use the explicit retry path when prompted.
1.7. Run several s3-compatible endpoints side-by-side¶
There's no fixed limit on how many s3-compatible connections you
can configure. Each one shows up in the swap-source cycle and in
the in-app Settings page. Example config covering a local MinIO, a
Cloudflare R2 production bucket, and a Backblaze B2 archive:
# <config-dir>/config.toml
[connections.minio-local]
kind = "s3-compatible"
credentials = "static"
endpoint_url = "http://localhost:9000"
region = "us-east-1"
access_key_id = "minioadmin"
secret_access_key = "minioadmin"
force_path_style = true
verify_tls = false
[connections.r2-prod]
kind = "s3-compatible"
credentials = "static"
endpoint_url = "https://<account>.r2.cloudflarestorage.com"
region = "auto"
access_key_id = "<r2-key-id>"
secret_access_key = "<r2-secret>"
force_path_style = false
[connections.b2-archive]
kind = "s3-compatible"
credentials = "static"
endpoint_url = "https://s3.us-west-002.backblazeb2.com"
region = "us-west-002"
access_key_id = "<b2-key-id>"
secret_access_key = "<b2-secret>"
force_path_style = false
Then Shift+S cycles through all three (plus your AWS profiles and
local) on the focused pane. If you'd rather edit interactively, open
Settings (,) → "S3-Compatible Connections" section → "+ Add"
to enter the same data through the inline form, or use the per-row
Edit / Delete chips to manage entries already there. Saves are
atomic (tempfile + os.replace) so the config can't end up
half-written.
See docs/connections.md §4
for the full source-cycle semantics and the unreachable-skip behavior.
2. Switch the theme on the fly¶
2.1. One-off (session-only)¶
Two paths, both fire ThemeChangedMessage and reload the active
stylesheet instantly without a restart:
- Press
tto open the theme picker modal, arrow to the theme you want, hit Enter. - Press
Shift+T(T) to cycle straight to the next theme without the modal — handy when you just want to flip carbon ↔ voidline.
The command palette has two working global theme commands: : then
Theme picker opens the same picker as t, and : then Cycle theme
has the same effect as Shift+T. Per-theme dynamic entries such as
theme switch ▸ voidline remain deferred and are not registered, so use
Theme picker to select a specific built-in or custom theme.
2.2. Persistent¶
# <config-dir>/config.toml
[defaults]
theme = "voidline"
Theme names: carbon (default), voidline, lattice, amber,
solarized-light, github-light, one-light, nord, dracula,
gruvbox-dark. See theming.md §1 for
the full per-theme palette breakdown.
2.3. Add a custom theme¶
A full replacement bypasses the built-in composition, so a repository checkout must combine the raw built-in theme, then the shared operational layer, before installing a custom file:
# Substitute your platform's config directory (see docs/platforms.md).
THEME_DIR="$HOME/.config/aws-tui/themes"
mkdir -p "$THEME_DIR"
cat src/aws_tui/ui/themes/carbon.tcss \
src/aws_tui/ui/themes/operational-panes.tcss \
> "$THEME_DIR/midnight.tcss"
Edit midnight.tcss, then select it with t or : then Theme picker.
Including operational-panes.tcss retains the Glue and Athena borders and
focus styling that built-in themes receive automatically. See
theming.md for the full token table.
2.4. Tweak just one or two colors¶
Drop <config-dir>/theme.tcss and override what you need; the
overlay layers on top of the active built-in:
/* <config-dir>/theme.tcss */
.modal-title { color: #ff3df8; }
Footer { background: #050505; }
3. Customize a keybinding¶
Runtime status: The composition root installs handled overrides on the live Textual keymap through
BindingResolver. It validates action ids throughKeymapStoreand logs/falls back to defaults when an overlay is invalid. Handlerless deferred actions remain unbound.
Rebind copy (pane.copy) from c to Ctrl+Y:
# <config-dir>/config.toml
[keybindings]
"pane.copy" = "ctrl+y"
Bare y is reserved by glue.copy_table_ref for copying a selected Glue
table reference.
For a fallback list (try Ctrl+K first, fall back to :):
[keybindings]
"app.command_palette" = ["Ctrl+K", ":"]
3.1. Disable a default binding¶
Set the action to an empty list:
[keybindings]
"pane.delete" = [] # nope, no quick delete
On the next launch, an empty [keybindings] value removes the live
keybinding until you edit the config back.
3.2. See the active map¶
The full list of action IDs lives in
docs/keybindings.md and is the same set
declared in src/aws_tui/infra/keymap_store.py:DEFAULT_BINDINGS. There
is no --print-bindings CLI flag in v0.8; the launch path enters the
TUI directly.
3.3. Unknown action IDs fall back to defaults¶
If you overlay an action id that isn't in KeymapStore.DEFAULT_BINDINGS
(e.g. typo pane.cpy), startup logs the UnknownAction and falls back
to the default keymap. That's deliberate: a bad override should not make
the TUI unlaunchable, and the log still gives maintainers the exact
action id to fix.
4. Diagnose an interrupted transfer after a crash¶
Long-running transfers keep a local journal while work is active so a process crash leaves evidence of the interrupted operation. Automatic replay and persisted S3 multipart state remain deferred; this recipe documents the current journal and manual cleanup flow.
4.1. What gets saved¶
The production transfer path writes a durable begin line to
<cache-dir>/transfers/<id>.jsonl:
{"kind":"begin","transfer_id":"abc123abc123abcd","source_uri":"local:///x.bin","destination_uri":"s3://bucket/x.bin","bytes_total":104857600,"upload_id":null,"ts":"2026-06-13T23:45:11Z"}
On success, skip, failure, or cancellation, aws-tui records the terminal state
and immediately removes that journal. The schema can replay optional part
lines and an upload_id, but the current explicit S3 multipart implementation
does not persist those values across process restarts.
4.2. What happens on next launch¶
Startup does not scan or display interrupted journals today. Files that remain lack a terminal record and can be inspected as JSONL to identify source, destination, size, and start time:
{"kind":"begin","transfer_id":"abc123abc123abcd","source_uri":"local:///x.bin","destination_uri":"s3://bucket/x.bin","bytes_total":104857600,"upload_id":null,"ts":"2026-06-13T23:45:11Z"}
4.3. Manual cleanup¶
To remove journals after inspecting them:
rm -f "<cache-dir>"/transfers/*.jsonl
For S3 uploads that were interrupted outside aws-tui's normal cancel path, the 1-day MPU abort lifecycle rule is the server-side backstop.
4.4. What gets dumped on a crash¶
If aws-tui hits an unhandled exception, it writes
<cache-dir>/crash/<ts>.txt:
aws-tui crash dump
timestamp: 2026-06-14T12:00:00+00:00
exception: TypeError: unsupported operand type(s) for +: ...
== traceback ==
...
== log tail ==
... (last 1000 lines of aws-tui.log)
The writer reads backward across the active log and numbered rotations. It retains up to 1,000 lines while reading at most 1 MiB in total, so producing a crash report does not load every retained log into memory.
The crash dump writer is live in v0.8.x. The interactive crash modal below exists as UI scaffolding but is not wired into the unhandled exception path yet:
unexpected error
TypeError: ...
<cache-dir>/crash/2026-06-14T12-00-00.txt
[view trace] [continue] [quit]
The planned continue button is enabled only when the last user action was
read-only (navigation, refresh, filter, palette open). Writes
(delete, copy, move, rename) disable it — you can't safely continue a
write that may have partially executed.
5. Browse AWS Glue safely¶
Glue is an AWS-only, read-only service in aws-tui. Select Glue in the nav rail, then use:
1for Catalog databases, tables, schema/storage detail, partitions, and column statistics;2for job definitions and recent runs;3for crawler status, configuration, metrics, and latest crawl;rto refresh the active view;Shift+Sto rebuild Glue under the next configured AWS connection.
The source header shows the configured connection name, distinct profile name when present, and region. Switching profiles clears the old page before the replacement VM loads, and remembered selections are isolated by connection name and region.
For the shared source, state, and table-reference workflow:
:opens the command palette. ItsSwitch sourcecommand invokesapp.swap_sourceand cycles resolver order; it does not select an exact source.- Use
Tab/Shift+Tabto focus the bordered source selector, then pressEnterorSpaceto open it. UseUp/Down, commit withEnter, and cancel withEscape. - In Glue Jobs press
Shift+F; in Crawlers pressShift+G. - In Athena press
Shift+W,Shift+C, orShift+Dfor the corresponding context selector. - In Glue Catalog select a table and press
yto copy its canonical, fully quoted identifier. - In Athena press
ioutside the editor, or choose Insert copied table reference, to replace the editor selection or insert at the cursor. - A connection/region mismatch is refused without changing the editor, clipboard, or active profile.
This is the copy table reference workflow for the typed app clipboard.
5.1. Least-privilege Glue permissions¶
Grant only the read operations needed by the views you use. A complete policy for the shipped Glue page may include:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"glue:GetDatabases",
"glue:GetTables",
"glue:GetTable",
"glue:GetPartitions",
"glue:GetColumnStatisticsForTable",
"glue:GetJobs",
"glue:GetJobRuns",
"glue:GetCrawlers",
"glue:GetCrawler",
"glue:GetCrawlerMetrics",
"glue:GetTags"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": "sts:GetCallerIdentity",
"Resource": "*"
}
]
}
AWS IAM and Lake Formation policies remain authoritative. An
AccessDenied response is shown only in the affected Glue pane; it
does not mark the connection unreachable or remove that profile from
other services.
5.2. Open a table location in S3¶
- In Catalog, select the database and table.
- Open the command palette with
:orCtrl+K. - Choose Open table location in S3.
aws-tui validates that the selected table has an s3:// location,
resolves the exact Glue connection name, verifies its region still
matches, rebuilds S3 through the normal service factory, and navigates
the left pane to /<bucket>/<prefix>. Missing/malformed locations,
removed connections, and region mismatches produce an advisory and do
not navigate. The advisory and logs omit the full URI.
Browsing the destination normally needs s3:ListAllMyBuckets for the
initial S3 root load and s3:ListBucket for the target bucket/prefix;
reading an object also needs s3:GetObject.
5.3. Exercise Glue in demo mode¶
Launch aws-tui --demo. demo-dev and demo-prod expose disjoint
catalog, job/run, and crawler names, so Shift+S visibly proves
profile isolation. demo-shared demonstrates a Glue access-denied
state. The Catalog-to-S3 command uses the matching synthetic profile
and never makes a real AWS call.
6. Run Athena queries safely¶
Athena is an AWS-only, read-only query service. Select Athena in the
nav rail and choose a workgroup, catalog, and database in the page header.
Each is a bordered, keyboard-focusable selector; use Shift+W, Shift+C,
or Shift+D to focus the corresponding control and commit or cancel it with
the shared picker workflow above.
The four views are Query (1), History (2), Results (3), and Saved (4).
Shift+S rebuilds the whole Athena page for the next supported AWS connection;
the old page is disposed, so rows, selections, loaders, result fetches, and any
app-owned active query do not cross profiles or regions. Selections may be
remembered only within the same connection name and region and are revalidated
when the page returns.
6.1. Minimum Athena and data permissions¶
Start with the least privilege required for the views in use. The AWS Service Authorization Reference defines each Athena action and its resource types. This is the minimum Athena API policy used by aws-tui; scope workgroup and data-catalog resources where the action supports resource-level permissions:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": [
"athena:ListWorkGroups",
"athena:GetWorkGroup",
"athena:ListDataCatalogs",
"athena:ListDatabases",
"athena:ListTableMetadata",
"athena:ListQueryExecutions",
"athena:GetQueryExecution",
"athena:BatchGetQueryExecution",
"athena:GetQueryRuntimeStatistics",
"athena:StartQueryExecution",
"athena:StopQueryExecution",
"athena:GetQueryResults",
"athena:ListNamedQueries",
"athena:BatchGetNamedQuery",
"athena:ListPreparedStatements",
"athena:GetPreparedStatement"
],
"Resource": "*"
}]
}
The query principal also needs access to catalog metadata and source data.
For ordinary IAM-controlled S3 tables, grant s3:ListBucket on the source
bucket/prefix and s3:GetObject on every underlying source-data object.
Cross-account source buckets also need a permitting bucket policy. AWS
documents this pass-through model in
Control access to Amazon S3 from Athena.
For Lake Formation-governed data, grant the required Lake Formation
DESCRIBE and SELECT permissions and IAM
lakeformation:GetDataAccess. Lake Formation uses that IAM action to vend
temporary credentials to Athena. DATA_LOCATION_ACCESS permits creating or
altering Data Catalog resources that point at a registered location; it is not
a query permission and is not required merely to read an existing table. See
Manage Lake Formation and Athena user permissions
and
Underlying data access control.
Source data encrypted with a customer managed KMS key requires kms:Decrypt
for the source key in IAM and in the key policy. For customer-managed S3
results, require kms:GenerateDataKey and kms:Decrypt for the result key. An
encrypted Glue Data Catalog additionally requires kms:GenerateDataKey,
kms:Decrypt, and kms:Encrypt. AWS lists these separate source, result, and
catalog requirements in
Encryption at rest.
6.2. Customer S3 output versus managed results¶
The shipped Query view sends the selected workgroup and query execution
context without a caller-side ResultConfiguration; its
AthenaQueryVM runner does not pass output_location to the domain client.
The lower-level AthenaClient.start_query(...) contract is broader: callers
may pass an optional output_location, in which case the client sends exactly
ResultConfiguration.OutputLocation. The shipped query path intentionally
omits it and therefore requires the workgroup to use one of two distinct output
modes:
- Customer S3 output. A workgroup-enforced customer S3 output location is
authoritative when
EnforceWorkGroupConfigurationis enabled. Grants3:GetBucketLocation,s3:ListBucket, ands3:ListBucketMultipartUploadson the result bucket, pluss3:PutObject,s3:AbortMultipartUpload,s3:ListMultipartUploadParts, ands3:GetObjecton the result prefix. Athena uses multipart uploads for query results, including partial failed or cancelled output.s3:GetObjectis also required to retrieve output and to browse the artifact through S3. See Work with query results and recent queries and the AWS S3 API permission mapping. - Athena managed results. Managed results do not create a customer S3
result artifact. They remain available through Athena for 24 hours and
managed results do not support result reuse. aws-tui continues to page rows
with
GetQueryResults; no S3 output location is required. If the workgroup uses a customer managed KMS key, both the query principal and the managed results key policy need the documented KMS access, includingkms:Decrypt,kms:GenerateDataKey, andkms:DescribeKey. See Managed query results.
The Query view labels the workgroup mode as managed results or S3 output. If neither mode is configured, aws-tui shows the typed result-configuration error instead of choosing a bucket. With managed results, Open Athena result in S3 remains on Athena and shows an advisory because there is no customer S3 artifact. Workgroup-enforced customer S3 and managed output are never treated as interchangeable.
When Athena rejects StartQueryExecution, Execution detail distinguishes an
inaccessible workgroup result destination, a rejected catalog/database
context, and a rejected workgroup. These categories use fixed copy and do not
echo raw SQL, S3 locations, profiles, workgroups, catalogs, databases, tables,
or request tokens. Other validation failures retain the generic safe fallback.
6.3. Exact read-only SQL grammar¶
Press Ctrl+Enter only after setting workgroup, catalog, and database. The
local sqlglot Athena-dialect parser fails closed and permits exactly one
statement from the following implemented grammar.
SELECT, set operations, and VALUES
A root SELECT may contain a VALUES relation, read-only common table
expressions, and read-only subqueries. A root set operation may use SELECT or
VALUES operands with UNION, INTERSECT, or EXCEPT, including the
parser-supported ALL or DISTINCT qualifier (or the default DISTINCT).
For each operation (UNION, INTERSECT, or EXCEPT), the matching forms
accepted by the current parser are:
operation [ALL|DISTINCT] BY NAME [ON (column-list)]operation [ALL|DISTINCT] [STRICT] CORRESPONDING [(ON|BY) (column-list)]
ALL or DISTINCT must appear immediately after the operation and before the
matching modifier; forms such as UNION BY NAME ALL and UNION CORRESPONDING
DISTINCT are rejected. The local policy does not resolve or validate matching
column names, compare them to either operand, or impose extra semantic rules on
the parser-accepted parenthesized column list. A standalone VALUES root is
rejected; it becomes allowed only below a SELECT or a root set operation.
Nested DDL, DML, commands, INTO, locks, analysis, execution, or transaction
nodes are rejected.
SHOW
These are the complete accepted forms. A catalog, database, and table may
be a regular or backtick-quoted identifier; patterns are string literals. A
SHOW TBLPROPERTIES property selector is a string literal only, not a regular
or backtick-quoted identifier.
SHOW DATABASES|SCHEMAS [IN catalog] [LIKE 'pattern']SHOW TABLES [IN [catalog.]database] ['pattern']SHOW COLUMNS FROM|IN table, wheretablemay have one, two, or three parts. A one-part table may additionally useFROM|IN [catalog.]database; that second scope is rejected after a multi-part table.SHOW PARTITIONS table, wheretablehas one, two, or three parts.SHOW TBLPROPERTIES table [('property')], wheretablehas one, two, or three parts.SHOW VIEWS [IN [catalog.]database] [LIKE 'pattern']
SHOW CREATE TABLE, SHOW CREATE VIEW, unknown verbs, missing required
targets, extra clauses, and write text appended to an otherwise allowed form
are rejected. SHOW TABLES uses its optional bare string pattern; LIKE in
that position is not accepted by this policy.
DESCRIBE
DESCRIBE [EXTENDED|FORMATTED] [database.]table accepts a one- or two-part
table name, not a three-part catalog/database/table name. It may then contain,
in this order:
PARTITION (key = value [, ...]), with one or more identifier keys and each value restricted to literal string or number equality; and- one root column identifier followed only by dotted identifier fields or
the exact complex selectors
'$elem$','$key$', or'$value$'.
The partition and column selector are independently optional. Operators other
than =, booleans, column references, calls, subqueries, empty or trailing
partition entries, unknown dollar selectors, array indexing, expressions, and
trailing identifiers are rejected.
EXPLAIN
EXPLAIN accepts another statement that independently satisfies this
allowlist. Its optional parenthesized choices are
FORMAT GRAPHVIZ|JSON|TEXT and TYPE DISTRIBUTED|IO|LOGICAL|VALIDATE, at most
once each, in either order when comma-separated. EXPLAIN ANALYZE, unknown or
quoted option names, unknown option values, duplicate options, and an unsafe
or missing body are rejected.
Accepted examples (one statement per line):
SELECT orderkey FROM analytics.orders LIMIT 10
SELECT * FROM (VALUES (1), (2)) AS samples(value)
SELECT 1 UNION ALL VALUES (2)
SELECT a, b FROM left_relation UNION BY NAME SELECT a, b FROM right_relation
SELECT a, b FROM left_relation UNION ALL BY NAME ON (a, b) SELECT a, b FROM right_relation
SELECT a, b FROM left_relation UNION DISTINCT BY NAME ON (a, b) SELECT a, b FROM right_relation
SELECT a, b FROM left_relation UNION CORRESPONDING SELECT a, b FROM right_relation
SELECT a, b FROM left_relation UNION ALL CORRESPONDING ON (a, b) SELECT a, b FROM right_relation
SELECT a, b FROM left_relation UNION DISTINCT CORRESPONDING BY (a, b) SELECT a, b FROM right_relation
SELECT a, b FROM left_relation UNION STRICT CORRESPONDING ON (a, b) SELECT a, b FROM right_relation
VALUES (1) INTERSECT SELECT 1
VALUES (1) EXCEPT VALUES (2)
SHOW DATABASES IN AwsDataCatalog LIKE 'analytics*'
SHOW SCHEMAS
SHOW TABLES IN AwsDataCatalog.analytics '*logs'
SHOW COLUMNS FROM AwsDataCatalog.analytics.orders
SHOW COLUMNS FROM orders FROM AwsDataCatalog.analytics
SHOW PARTITIONS AwsDataCatalog.analytics.orders
SHOW TBLPROPERTIES analytics.orders('comment')
SHOW TBLPROPERTIES `orders table`('comment')
SHOW VIEWS IN AwsDataCatalog.analytics LIKE 'orders*'
DESCRIBE FORMATTED analytics.orders
DESCRIBE analytics.orders PARTITION (`event date` = '2026-07-25', shard = 7) payload.'$elem$'.field
EXPLAIN (TYPE VALIDATE, FORMAT GRAPHVIZ) SELECT 1
EXPLAIN (TYPE IO, FORMAT TEXT) DESCRIBE analytics.orders payload.'$key$'.'$value$'
Rejected examples (one statement per line):
VALUES (1), (2)
SELECT 1; SELECT 2
SHOW CREATE TABLE analytics.orders
SHOW COLUMNS
SHOW TABLES IN analytics LIKE 'orders*'
SHOW TBLPROPERTIES analytics.orders(comment)
SHOW TBLPROPERTIES analytics.orders(`comment`)
SELECT a FROM left_relation UNION BY NAME ALL SELECT a FROM right_relation
SELECT a FROM left_relation UNION CORRESPONDING DISTINCT SELECT a FROM right_relation
DESCRIBE AwsDataCatalog.analytics.orders
DESCRIBE orders PARTITION (shard > 1)
DESCRIBE orders payload.'$unknown$'
EXPLAIN ANALYZE SELECT 1
EXPLAIN (FORMAT YAML) SELECT 1
EXPLAIN (TYPE IO, TYPE LOGICAL) SELECT 1
CREATE TABLE copy AS SELECT * FROM analytics.orders
INSERT INTO archive SELECT * FROM analytics.orders
UNLOAD (SELECT 1) TO 's3://example/results/'
CALL system.runtime.kill_query()
Empty or unparsable input, multiple statements, all other DDL and DML, CTAS,
and every form outside the grammar above are also rejected before any
start_query_execution call. The editor retains the rejection as validation
feedback and does not dispatch the SQL. IAM, Lake Formation, workgroup, S3,
bucket, and KMS policies remain the authorization boundary.
6.4. Execution, History, Results, and result artifacts¶
After submission, Query records an app-owned execution identity and polls its
detail through queued, running, and terminal states. On SUCCEEDED, it loads
the first Results page; FAILED and CANCELLED retain terminal detail. Esc
interrupts query submission before an execution is available and stops only an
active app-owned execution started by this page through stop_query_execution.
Switching context or source requests cancellation and awaits Athena shutdown
before disposal; History rows are never assumed safe to stop.
Results are fetched one page at a time and never fully materialized. l loads
the next page only when Athena returns a continuation token; each domain
request asks for at most 1,000 rows. Query execution detail reports bytes
scanned. History hydrated detail reports bytes scanned and whether Athena
reused a previous result. Results is the authoritative paged row surface, not
the cost-statistics surface.
History lists execution IDs for the selected workgroup and hydrates the
selected execution's detail. Choose a successful execution and open Results to
fetch its rows. The Saved view separately pages named queries and prepared
statements; selecting a prepared statement calls GetPreparedStatement for
its SQL. Open in query editor copies SQL without executing or bypassing the
read-only policy.
For customer S3 output, choose Open Athena result in S3 from the command
palette. The Results VM does not trust History hydrated detail for navigation:
it performs an authoritative reload with GetQueryExecution, requires a
succeeded execution, exact execution ID, active connection, region and query
context, and a valid s3:// output location. It then rebuilds S3 under the
exact connection and region, reveals that result artifact, and selects it.
Missing, managed, malformed, non-S3, foreign-context, or unsucceeded output
leaves the user on Athena with a redacted advisory; no fallback profile is
substituted.
Treat bytes scanned as the cost signal before broad queries. aws-tui does not calculate a currency price, and a workgroup bytes-scanned cutoff can reject or limit work.
6.5. Exercise Athena in demo mode and troubleshoot¶
Launch aws-tui --demo, select Athena, and begin on demo-dev. Its
dev-analytics workgroup has succeeded, running, failed, empty, missing-output,
and result-access-denied history scenarios, plus one named query and one prepared
statement. The successful q-dev-succeeded artifact opens at
s3://athena-results/dev/q-dev-succeeded.csv. demo-prod has different
workgroup, catalog, database, history, saved SQL, and a result artifact under
s3://athena-results/prod/; demo-shared returns a scoped Athena access-denied
state. All of this is in-memory and resets on launch.
- Athena access is forbidden. Verify the selected AWS profile, the Athena actions above, and the Lake Formation grants. The failure stays scoped to Athena and does not remove the profile from the source ring.
- Result configuration is required. Set an output location or managed query-results configuration on the selected workgroup. aws-tui will not silently send results to another bucket.
- Cannot access the workgroup result location. The workgroup can still be configured for enforced S3 output while the active principal lacks the required bucket, object, or KMS permissions. Check the result-prefix grants listed in §6.2; aws-tui does not replace the enforced destination.
- Selected query context or workgroup was rejected. Re-select the catalog,
database, and enabled workgroup for the active profile and region. The
generated SQL is database/table relative because the exact catalog and
database travel in
QueryExecutionContext. - No rows or cannot load more. A successful query can return no rows;
lonly works when Athena supplied another page token. Check the query state and execution detail in History. - Cannot open an artifact in S3. The execution must be successful and its
output URI must be valid, match the active connection/region, and be readable
through S3. Check
s3:ListBucket/s3:GetObjecton the result prefix.
7. Inspect and query Glue tables through Athena¶
Glue → Athena navigation is an explicit, read-only handoff. It carries the
selected table's catalog, database, table, connection name, and region in an
immutable TableRef; it does not pass a client or reuse a different profile.
- Select Glue, then choose a Catalog database and table.
- Open
:orCtrl+Kand choose Query table in Athena. -
Review the generated statement:
SELECT * FROM "database"."table" LIMIT 5 -
Edit it if needed, then press
Ctrl+Enterto execute.
aws-tui quotes the database and table identifiers independently, discovers the
destination catalog/database through bounded pagination, sends both through
Athena's query execution context, and selects an enabled workgroup for the same
source. The editor shows the starter statement as soon as Athena
mounts, even while remote context discovery is still in progress. Run remains
disabled and the query controls report RESOLVING TABLE CONTEXT until that
exact context is ready. aws-tui never executes generated queries
automatically. If the handoff is cancelled, superseded, or cannot mount the
destination, the composition root restores the prior Glue/Athena state instead
of leaving a partially switched page.
From an Athena Query view, Open query table in Glue is available through the command palette when the read-only SQL resolves to exactly one visible table. Queries with zero or multiple table references remain on Athena.
7.1. Iceberg detection and metadata views¶
Glue table detail classifies a table as Iceberg when normalized Glue
parameters such as table_type, tableType, classification, provider, or
spark.sql.sources.provider, or the Glue TableType, contains the exact
iceberg marker. Only then does the Catalog detail surface show Iceberg
metadata.
The tabs are:
- Snapshots — commit time, snapshot/parent IDs, operation, manifest list, and summary; bounded to 100 rows.
- History — current-snapshot ancestry; bounded to 100 rows.
- Manifests — manifest paths, lengths, partition-spec IDs, and file counts; bounded to 500 rows.
- Files — data/delete file paths, format, partition, record count, and bytes; bounded to 1,000 rows.
- Partitions — partition values and aggregate file/record/delete metrics; bounded to 500 rows.
- References — branches/tags, snapshot IDs, and retention fields; bounded to 100 rows.
Each tab loads on demand through IcebergInspector, which submits a bounded
read-only query to Athena metadata tables such as
"table$snapshots" and "table$files". The UI reveals 50 rows at a time;
Load more exposes already-bounded rows without broadening the Athena query.
Retry reruns only the active tab. A permission, throttling, shape, or
network error is a partial failure of that metadata pane and does not erase
successful sibling tabs.
These are real Athena queries, not free Glue lookups. Account for
metadata-query costs, workgroup limits, bytes scanned, result storage, and
concurrency exactly as for other Athena statements. The selected profile needs
the Glue read operations in §5.1, the Athena operations in §6.1, source
data/catalog authorization where Athena requires it, and result-location
permissions for its workgroup. Lake Formation-governed tables additionally
need lakeformation:GetDataAccess plus the relevant DESCRIBE/SELECT
grants. A denied metadata table is reported generically; raw SQL, table
metadata values, and provider exception text are not written to UI errors or
diagnostic logs.
7.2. Snapshot time travel¶
In Snapshots, highlight a visible snapshot and press Shift+V, click the
time-travel control, or choose Query Iceberg snapshot in Athena. The
destination editor receives:
SELECT * FROM "database"."table"
FOR VERSION AS OF 4201 LIMIT 5
The snapshot ID must be a non-negative integer present in the visible snapshot
page. Switching to another metadata tab clears the actionable snapshot
selection. As with an ordinary table handoff, generated SQL remains
unexecuted until Ctrl+Enter; review the catalog, database, table, workgroup,
snapshot ID, and expected scan before running it.
After a successful customer-S3 execution, Open Athena result in S3 authoritatively reloads the execution and reveals its exact CSV artifact under the same connection name and region. Athena managed results have no customer S3 artifact, so the command remains on Athena.
7.3. Demo journey and limitations¶
Launch aws-tui --demo and follow this no-network path:
- On
demo-dev, open Glue tabledev_analytics.dev_events_iceberg. - Inspect its metadata tabs, choose snapshot
4201, and pressShift+V. - Confirm Athena contains
FOR VERSION AS OF 4201and has not started an execution. - Press
Ctrl+Enter, inspect the two profile-specific result rows, then open the generated CSV in S3. - Use
Shift+Sto compare the disjointdemo-proddata and the scopeddemo-sharedaccess state.
Current scope is read-only operational visibility. aws-tui does not create, alter, optimize, expire, rollback, branch, tag, or delete Iceberg resources; it does not create/edit/delete Glue or Athena resources; and it does not infer a write workflow from metadata. Large metadata tables remain bounded by the limits above, so the UI is an operational inspection surface rather than a complete metadata export tool.
7.4. Local row preview with DuckDB (Peek)¶
The Peek tab previews rows of the selected Iceberg table by querying its
S3 location directly with a local DuckDB engine — no Athena workgroup, no
query execution, no AWS query bill. It needs the optional duckdb extra
(pip install aws-tui[duckdb]) and an AWS profile connection; it is not
available on s3-compatible connections. Without the extra installed, the
tab is still present and selectable; choosing it reports the missing engine
and states the install command instead of disappearing silently.
- Select Glue, open an Iceberg table, and switch to the Peek tab (it sits after Refs in the Iceberg tab strip).
- aws-tui runs one
iceberg_scan(...)statement against the table's S3 root, pinned to the selected snapshot when one is chosen on Snaps. Rows, columns, andNULLs (dimmed, distinct from a column that genuinely contains the four-character string"NULL") render in the same table widget the six metadata tabs use. - The footer reports the exact row count and the row-limit ceiling, e.g.
100 rows · limit 100. Unlike the six metadata tabs' load-more control, which only widens an already-capped local window over rows already in hand, Peek's↓issues a genuinely new query at the next row-limit step (100 → 1,000 → 10,000); it disables once the top step is reached or a page comes back short of the limit. ↻retries the current query, honoring the active snapshot pin and row limit.
DuckDB resolves AWS credentials itself, through a fresh credential_chain
secret bound to the connection's exact profile and region on every query —
never the ambient default credential chain, and never a cached token, so an
aws sso login run in another terminal is picked up on the next ↻ the same
way an Athena query already is. Queries run entirely on your machine: the
engine reads S3 objects directly with whatever permissions your profile
already grants on that bucket, and it never touches Athena, a workgroup, or a
query-result location, so it incurs none of the costs or setup in §6 and
§7.1. The one exception is setup, not querying: the first Peek query on a
machine downloads DuckDB's httpfs, aws, and iceberg extensions from
extensions.duckdb.org and caches them under ~/.duckdb/, so on a host
without egress to that domain the first query fails until the cache is
populated (see Installation). Forbidden, not-found, not-Iceberg, and expired-credential failures
surface through the same placeholder styling as the six metadata tabs.