Usage#
Quick Start gets tinytap running. This page
covers what to do once it’s up: reading the TUI, reading the stdout JSONL
format, and when to reach for doctor.
TUI#
output = "auto" (the default) opens the TUI whenever stdout/stdin are an
interactive terminal of at least 120x24. See
Configuration to force it with output = "tui" or opt out with output = "stdout".

Request table#
Every captured exchange is a row: timestamp, process[pid], method, path,
status, response size, latency. New rows arrive at the bottom; the view
follows the newest row automatically until you scroll away from it.
| Key | Does |
|---|---|
j / ↓, k / ↑ | Move the selection (stops following new rows) |
g / G | Jump to the oldest / newest row (G re-arms following) |
/ | Start typing a filter, matches live as you type |
Enter (while filtering) | Keep the filter, stop editing it |
Esc (while filtering) | Clear the filter |
Enter | Open the detail panel for the selected row |
d | Open the diagnostics panel |
q / Ctrl-C | Quit |
Detail panel#
Enter on a row opens its full request/response: headers, JSON body when
decodable, hex otherwise. From there:
| Key | Does |
|---|---|
Tab | Move focus into the detail panel to scroll long headers/bodies independently of the table |
j/k, g/G | Scroll the detail panel once it has focus |
b | Toggle hex/text body view |
Esc | Step back out: unfocus the panel, then close it |
Enter | Close the detail panel |
Diagnostics panel#
tinytap redirects its own internal log lines (process attach/detach,
TLS uprobe attach, teardown errors) into a diagnostics buffer instead of
printing them over the TUI, since a bare log.Printf mid-render would
corrupt the screen. Press d to view them; the footer shows ⚠ N diag (d)
whenever there are lines waiting. They’re flushed to stderr when tinytap
exits, so nothing is lost if you never open the panel. g/G jump to the
top/bottom; Esc, d, or Enter closes it.
stdout mode#
output = "stdout" prints one JSON object per exchange (JSONL) instead of
drawing the TUI, useful when piping into jq, running over SSH, or in CI.
Full request/response headers are always included, with no separate verbose
toggle; reqBody/resBody/abandonReason/ssl are omitted when empty
rather than sent as zero values:
{
"reqTsNs": 1754887677005000000,
"latencyNs": 300000,
"pid": 27122,
"fd": 6,
"comm": "python3",
"method": "GET",
"path": "/api",
"reqVersion": "HTTP/1.1",
"status": 200,
"reason": "OK",
"resVersion": "HTTP/1.1",
"resBytes": 1304,
"reqBytes": 0,
"reqHeaders": [{"name": "Host", "value": "localhost:8000"}],
"resHeaders": [{"name": "Content-Type", "value": "application/json"}],
"abandoned": false,
"reqBodyTruncated": false,
"resBody": "eyJvayI6dHJ1ZX0=",
"resBodyTruncated": false,
"sslFallback": false
}An abandoned exchange (the connection closed, or tinytap gave up waiting)
sets abandoned and abandonReason (peer closed or timed out);
status/resBytes stay at their zero value since no response arrived.
reqBody/resBody are base64 (JSON has no raw byte type); pipe through
jq -r '.resBody | @base64d' to decode one, or filter the whole stream by
field, e.g. tinytap --config … | jq 'select(.abandoned)'.
doctor#
tinytap doctor runs a read-only preflight (kernel version, capabilities,
libssl execute bit) without loading any eBPF. Run it first when something
doesn’t work; see Troubleshooting.
--version#
Prints the build’s version, commit, and date, and exits before touching eBPF at all. Safe to run without root or capabilities.