Skip to content

Record command history and add mapbox history - #56

Merged
zmofei merged 6 commits into
run-recordfrom
run-log
Sep 29, 2026
Merged

zmofei merged 6 commits into
run-recordfrom
run-log

Conversation

@zmofei

@zmofei zmofei commented Sep 28, 2026 •

Copy link
Copy Markdown
Member

Stacked on #57. #58 (diagnostic logs) and #46 (telemetry events) are stacked on this.

Adds command history: a local record of recent runs, read back by mapbox history, so a user or support can see what ran and how it ended.

  • Each run appends one line to ~/.mapbox/history/<UTC date>.jsonl, kept 30 days and at most 10 MB: the command path (search forward), invocation, exit code, error code, duration, request count and the last five request ids. No argument values, URLs, error messages or account — arguments carry search terms, file paths and ids.
  • On by default. mapbox config set history off turns it off; MAPBOX_HISTORY=0/=1 overrides that for a session. With it off, nothing is written and no directory is created.
  • Not recorded: --help, --version, completion, history itself, and runs under sudo (root-owned files would block the user's own runs, as in v2.33.9: Permission denied on ~/.aws/config when running as non-root user after initialization aws/aws-cli#10031).
  • mapbox history list shows recent runs, newest first. mapbox history show [id] shows one run, the newest by default, matched by any prefix of its id. New error codes: history_not_found, history_ambiguous_id, history_empty.
  • Files are written through a new dated_jsonl: private (0700/0600), one file per UTC day, pruned by age and size, and it deletes only files named exactly YYYY-MM-DD.jsonl (added to MAY_DELETE). Add diagnostic logs, shown through mapbox history show #58 and WIP: Record one cli.command telemetry event per run #46 reuse it.

Behavior changes

  • A run now creates ~/.mapbox/history/, and ~/.mapbox if missing, even for read-only commands. Tests that hold a command to creating nothing now run with history off; tests/history.rs checks what history leaves.
  • mapbox config list reports a second key, history.

Not checked: pruning happens on a day's first write, so with history off, old records stay until deleted (the README says so). The 10 MB limit is only unit-tested. The #[cfg(not(unix))] paths only compile in CI.

zmofei added a commit that referenced this pull request Sep 28, 2026
Rebuilt on top of the local run log (#56), which now carries the shared
parts: http::send and its guard, dated_jsonl, and cli() returning the exit
code. What is left here is telemetry's own: telemetry_event, telemetry_sink
(delivering through dated_jsonl), and one reporting call beside each of the
log's.

The event is still written only into a config directory that already
exists. Telemetry finishes before the log so that the log creating
~/.mapbox does not change that for the same run.
zmofei added a commit that referenced this pull request Sep 28, 2026
Built from the run_record the local log (#56) already collects, so no
call site changes here: telemetry_event chooses each field from the
record under its privacy rules, and telemetry_sink delivers the line
(to a local file for now, through dated_jsonl). run_record::finish hands
the record to telemetry before the log, because the event is written only
into a config directory that already existed and the log may create one.
@zmofei zmofei closed this Sep 28, 2026
@zmofei
zmofei deleted the run-log branch September 28, 2026 11:33
@zmofei
zmofei restored the run-log branch September 28, 2026 11:34
@zmofei zmofei reopened this Sep 28, 2026
@zmofei
zmofei changed the base branch from main to run-record September 28, 2026 11:34
@zmofei zmofei changed the title Add a local run log Record command history and add mapbox history Sep 28, 2026
@zmofei
zmofei added this pull request to stack #59 September 28, 2026 11:40
Each run appends one line of execution metadata to
~/.mapbox/history/<UTC date>.jsonl, kept 30 days: the command path from
the command tree, invocation, exit code, error code, duration, request
count and the last five request ids. Never an argument value, URL, error
message or account: arguments carry search terms, file paths and ids.
The file is written through dated_jsonl: private, one file per UTC day,
pruned to the retention window.

On by default. `mapbox config set history off` turns it off for good,
MAPBOX_HISTORY=0 or =1 for a session over the setting. With it off,
nothing is written and no directory is created. With it on, the config
directory is created when missing, so history works the same for someone
who only ever set MAPBOX_ACCESS_TOKEN. Not recorded: --help, --version,
completion, history itself, and runs under sudo, whose root-owned files
would stop the user's own runs appending (aws/aws-cli#10031).

`mapbox history list` shows the most recent runs, newest first, and
`mapbox history show [id]` one run, the newest by default, found by any
prefix of its id.

The read-only-command tests that held ~/.mapbox untouched now check both
sides: with history on only `history/` may appear, with it off nothing.
- `history list` text output gets a header row.
- Tests that hold a run to leaving nothing on disk turn history off in
  their helper instead of looping over both states; tests/history.rs
  checks what history leaves, and that skipped runs create nothing.
- README says turning history off keeps what was already recorded.
- Drop the setting count from config.rs's module doc, which every new key
  would have to edit.
Moves dated_jsonl::shed here from the diagnostic-log PR so history, on by
default, is bounded by size as well as by age. trim now keeps exactly the
bytes shed hands it; the margin was applied twice before.
For each run command history records, `mapbox config set log on` (or
MAPBOX_LOG=1) adds a line of detail to ~/.mapbox/logs/<UTC date>.jsonl,
linked to the history record by its id: the command line with tokens
redacted, which token was used, each request (method, redacted URL,
status, request id, timing) and the error message. Off by default.

Logging needs history. With history off it never runs, and `config set
log on` refuses with history_required rather than store a setting that
does nothing. A run history doesn't record gets no log either, since
nothing could lead back to it.

`mapbox history show` includes the log and says what became of it:
diagnostics.status is captured, not_captured (logging was off) or
unavailable (captured, since expired or evicted). The history record
carries diagnosticsCaptured so the last two can be told apart. There is
no separate logs command.

Logs are kept up to 30 days and 100 MB in total. Past the limit the
oldest go first, down to the line, and their history records stay. On
every run, a day of logs whose day of history has expired or gone is
deleted, logging on or off.
@zmofei
zmofei marked this pull request as ready for review September 28, 2026 12:26
@zmofei
zmofei requested a review from a team as a code owner September 28, 2026 12:26
- History and the log each read the clock, so a run finishing across UTC
  midnight could put its log a day after its history, and the same run's
  cleanup then deleted it. Both lines now share one time and one day's
  file, which also drops next_date.
- The log no longer repeats what the history record has (command,
  invocation, exitCode, durationMs, version).
- scrub_tokens now finds a token that starts inside a word, as after a
  percent-encoded `=` (`%3Dpk.`) or in a short-flag cluster (`-ytpk.`).
- tests/history.rs clears MAPBOX_LOG like the other suites.
- README: a day of logs goes with its day of history, and the refusal
  follows the `history` setting, not MAPBOX_HISTORY.

@mattpodwysocki mattpodwysocki left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Built and tested this too, live, not just read the diff: ran a few real commands with a scratch MAPBOX_CONFIG_DIR, checked mapbox history list/show in both output modes, toggled config set history off and MAPBOX_HISTORY=1 to confirm the override actually overrides, and checked file permissions on disk (0600 on the files, 0700 on the directory). Everything matched what the PR body claims, including the privacy boundary: I grepped the actual jsonl line and confirmed no argument values, URLs, or account info are in it, only what the module doc promises.

Also traced through the id-prefix matching, the not-found/ambiguous errors, and the shed/prune math by hand against the existing unit tests (the three-day, 100-byte-line shedding test correctly drops the oldest whole day, then trims the next to make up the remainder, leaving the newest day whole).

One thing worth naming since it compounds: finish_locked now calls run_history::write directly rather than main() orchestrating it. Given the panic hook has to call finish_locked too (main() never gets control back after a panic), this is probably the right call, calling out to consumers from inside finish_locked is the only way a panicking run still gets a history line, but it does mean run_record is accumulating direct knowledge of every consumer as more of these land. Not asking for a change here, just flagging it for whoever's stacking #46 on top.

Approving.

@zmofei
zmofei removed this pull request from stack #59 September 29, 2026 08:37
Add diagnostic logs, shown through mapbox history show
@zmofei
zmofei merged commit e6756a9 into run-record Sep 29, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants