CLI tools

Diagnostics: what a command did

When a command fails or takes too long, tg can show what it did, keep a record of it, and turn the record into a report you can attach to an issue. None of it holds message text.

Show it: --trace

tg --trace messages list "Book club" --limit 5

--trace prints each operation on stderr as it happens: → what was asked, ← what came back, with ids, counts, duration and an error code if there was one.

→ messages.list    chat -1001234567890
← messages.list    chat -1001234567890  118ms  5 messages

It also passes on the log lines of the Telegram library underneath. stdout is unchanged, so a pipe still gets only data.

Keep it: --record

tg --record chats list
tg runs list                 # recorded runs, newest first
tg runs show <run-id>        # one run: its outcome, and one line per operation
tg runs path <run-id>        # the directory that holds it

A run is a directory under runs/<day>/ in the state directory (~/.local/share/tg-cli/runs/ on Linux), named by its time and its command, with two files:

  • run.json — the command, the profile, the version of tg, Node and the system, when it started and ended, how many requests it made, the outcome and the error code;
  • events.jsonl — the same operations --trace shows, one per line.

A failed run is always kept

When a command ends in an error, its run is kept even without --record, marked "keptBecauseFailed": true in run.json. That holds for every command and every error: a bad option, an unknown command, a check before any work, commands that never connect (models, server, upgrade). A failure before the command even started, such as a config file that does not load, is kept as a run named tg. The record holds only the command's words, such as messages list, never what followed them.

A run that worked leaves nothing unless you asked. So a problem report always has a failure to attach, and a history of what you read does not build up. --no-record, or "record": false in the settings, turns this off too.

When to record every run

tg config set record true          # this profile
tg --no-record chats list          # but not this one

Then every run is kept. The default is the other way round on purpose: a run that worked is not written until you ask. A messenger that keeps a folder of whom you read and when would be a diary of your life nobody asked for.

How long it lives

30 days, or keepRunsForDays in the settings. Old runs are removed only when a new one is kept: a tool that writes nothing has no reason to walk the folder. They are removed a whole day at a time, by the folder's name, so nothing has to be opened to decide.

What is never in a record

A recorded run and --trace carry an operation's name, ids, counts, durations and error codes. They never carry:

  • the text of a message, or a caption;
  • a chat title, a person's name or a username;
  • what you typed as <chat>, because a typed chat is often a title;
  • a phone number, a login code, a 2FA password, the session or the app hash.

The same goes for a report made from a run.

Check the installation: tg doctor

tg doctor              # connects to nothing
tg doctor --online     # also connects once and reads the account; sends nothing

It shows the version, the runtime, the profile, the config file, whether a session and the app credentials exist (never their values), whether TG_*_DIR moved the keyring entry, the local store (its path, its version, and how many chats and messages it holds), the sends of the last hour, and the runs kept.

A problem report

tg doctor report create                   # about the newest failed run
tg doctor report create --run <run-id>    # about this one

It writes a JSON file — what tg doctor shows plus the run — and says where to send it: a new issue at github.com/leemour/tg-cli/issues. Read it before you send it. It holds no message text, and ids appear as labels, not as Telegram's numbers.

If no failed run is kept, run the failing command again; its failure is kept by itself.

What to do with runs

The records are JSON, so jq answers questions about them. tg runs list --json answers { items, page, limit, hasMore }, and items are the runs' run.json, newest first:

tg runs list --limit 100 --json | jq '[.items[] | select(.status == "failed") | {command, errorCode}]'
tg runs list --limit 100 --json | jq '[.items[] | .durationMs] | add / length'    # average duration
tg runs list --limit 100 --json | jq '[.items[] | select(.requests > 10) | {command, requests}]'

tg runs show <run-id> prints the same events as a table, without the fields every line repeats. The whole file is in the folder tg runs path <run-id> prints.

Next

On this page