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 messagesIt 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 itA 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 oftg, 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--traceshows, 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 oneThen 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 nothingIt 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 oneIt 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
- troubleshooting.md — what an error means and what to do
- security.md — what reaches the disk at all