The most accurate record of how a server was set up is the shell history of whoever set it up. It is also the least readable: hundreds of lines of typos, retries, ls and cd, with the three commands that mattered buried somewhere in the middle.
An LLM is good at turning that into a how-to, a README section, or an onboarding note. It is not good at knowing which commands worked, and it will tidy your real commands into ones you never ran. The work is in feeding it clean, complete, secret-free input and then checking that the document still matches what actually happened.
Make History Worth Feeding to a Model
Default bash history is lossy: no timestamps, a 500-line cap, and the last terminal to close overwrites everyone else. Fix that before you need it:
# ~/.bashrc
HISTTIMEFORMAT='%F %T ' # record when each command ran
HISTSIZE=100000 # lines kept in memory
HISTFILESIZE=200000 # lines kept in ~/.bash_history
HISTCONTROL=ignoreboth # skip repeats and space-prefixed lines
HISTIGNORE='ls:ll:pwd:clear:exit:history*' # drop pure noise
shopt -s histappend # append on exit, never overwrite
PROMPT_COMMAND="history -a; $PROMPT_COMMAND" # write each command as it runs
With HISTTIMEFORMAT set, bash stores an epoch timestamp line before each command in the history file:
$ tail -4 ~/.bash_history
#1790863331
sudo apt install -y nginx
#1790863350
sudo systemctl enable --now nginx
Timestamps are not retroactive β commands saved before you set HISTTIMEFORMAT have no timestamp line. Set it now so next month's session can be extracted by time.
Keep cd out of HISTIGNORE β it looks like noise, but the working directory is often the missing context that makes a relative path in the doc make sense.
history -a is what saves you β without it, commands only reach the file when the shell exits cleanly. A dropped SSH session loses the lot.
Extract One Session, Not Your Whole History
A model given 20,000 lines of history produces a vague summary of your career. Give it the two hours where you did the thing:
# From the current shell, using the formatted timestamps
$ HISTTIMEFORMAT='%F %T ' history \
| awk '($2" "$3) >= "2026-10-01 14:00" && ($2" "$3) <= "2026-10-01 16:30"' \
| sed -E 's/^ *[0-9]+ +//'
# From the file, which includes other terminals once history -a has run
$ s=$(date -d '2026-10-01 14:00' +%s); e=$(date -d '2026-10-01 16:30' +%s)
$ awk -v s="$s" -v e="$e" '
/^#[0-9]+$/ { ts = substr($0, 2) + 0; next }
ts >= s && ts <= e' ~/.bash_history > /tmp/session.txt
History does not record whether a command succeeded, and that is the fact a document most needs. A small prompt hook logs the exit status next to every command:
# ~/.bashrc - replaces the PROMPT_COMMAND line above
__log_cmd() {
local rc=$?
history -a
printf '%s\t%s\t%s\n' "$(date '+%F %T')" "$rc" "$(fc -ln -1 | sed 's/^[[:space:]]*//')" >> ~/.cmd_status.log
}
PROMPT_COMMAND=__log_cmd
# Later: only the commands that exited 0 in the window
$ awk -F'\t' '$1 >= "2026-10-01 14:00" && $1 <= "2026-10-01 16:30" && $2 == 0 { print $3 }' \
~/.cmd_status.log | uniq > /tmp/session.txt
Exit 0 is not the same as worked β grep exits 1 when nothing matches, and a misconfigured service can start with exit 0. The status log is a filter, not a verdict.
Empty prompts repeat the last line β pressing Enter on a blank prompt logs the previous command again; uniq collapses those adjacent duplicates.
date -d is GNU β on macOS or BSD use date -j -f instead, or compare the formatted timestamps as strings as the first example does.
Capture Full Sessions with script or asciinema
History has the commands but not the output. When you know in advance that you are doing something worth writing up, record the whole terminal. The model then sees the error you hit and the change that fixed it:
# util-linux script: everything displayed, plus timing for replay
$ script --log-timing ~/sessions/nginx.tm ~/sessions/nginx.log
# ... do the work, then:
$ exit
$ scriptreplay --log-timing ~/sessions/nginx.tm ~/sessions/nginx.log
# asciinema: same idea, shareable .cast file
$ asciinema rec ~/sessions/nginx.cast
$ asciinema cat ~/sessions/nginx.cast > ~/sessions/nginx.log
# Strip colour codes and carriage returns before any model sees it
$ sed -E 's/\x1b\[[0-9;?]*[A-Za-z]//g; s/\r$//' ~/sessions/nginx.log | col -b > /tmp/session.txt
Output is the valuable part β "the first certbot run failed because port 80 was firewalled" is exactly the sentence a how-to needs, and it only exists in the output.
It records what was displayed β passwords typed at no-echo prompts are not captured by default, but anything printed β cat .env, env, a token echoed by a CLI β is. Treat a session log as more sensitive than history, not less.
Scrub Secrets Before Anything Leaves the Box
Shell history is one of the most common places credentials leak from. Search for them deliberately, with line numbers, and read every hit:
$ grep -nEi '(pass(word|wd)?|secret|token|api[_-]?key)[=: ]' /tmp/session.txt
$ grep -nE 'AKIA[0-9A-Z]{16}|gh[pousr]_[A-Za-z0-9]{36}|xox[abpr]-[A-Za-z0-9-]+' /tmp/session.txt
$ grep -nE 'eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+' /tmp/session.txt # JWTs
$ grep -nE -- '-----BEGIN [A-Z ]*PRIVATE KEY-----' /tmp/session.txt
$ grep -nE '://[^/:@ ]+:[^/@ ]+@' /tmp/session.txt # user:pass@host
$ grep -nE 'Authorization: *(Bearer|Basic)|mysql.* -p[^ ]|sshpass -p' /tmp/session.txt
# Redact rather than delete, so the document keeps its shape
$ sed -E \
-e 's/(AKIA)[0-9A-Z]{16}/\1REDACTED/g' \
-e 's#(://[^/:@ ]+:)[^/@ ]+@#\1REDACTED@#g' \
-e 's/((pass(word)?|token|secret)=)[^ ]+/\1REDACTED/Ig' \
/tmp/session.txt > /tmp/session-clean.txt
Then stop putting secrets on the command line in the first place:
# With ignorespace or ignoreboth, a leading space keeps a line out of history
$ export API_TOKEN=abc123
# Better: prompt for it, so it is never typed as an argument at all
$ read -rs API_TOKEN && export API_TOKEN
# Already recorded? Delete the entry (bash 5.0+ accepts negative offsets)
$ history -d -2
Patterns miss things β internal hostnames, customer names in paths, and bespoke token formats will not match any regex. Test your patterns in the regex tester, then still read the file.
history -d only edits memory β with history -a running, the line is already in ~/.bash_history. Edit the file too, and rotate the credential β deleting the record does not un-leak it.
Command-line secrets leak elsewhere β arguments are visible to other users in ps and /proc while the command runs. The leading-space trick hides them from history only.
Prefer a local model β even scrubbed, a session reveals your layout. Running the model with Ollama keeps it on your machine.
Prompt for the Document You Actually Need
"Document this" gets you a chronological retelling. Decide what kind of document you want first, because each is organized differently:
| Document | Reader | Organized by | Ask for |
|---|---|---|---|
| How-to | someone repeating the task | steps toward one goal | numbered steps, a check after each |
| Reference | someone maintaining the system | topic: files, services, ports | tables of paths and settings, no narrative |
| Onboarding note | a new team member | what exists and why | orientation first, commands second |
$ ollama run llama3.2 "Turn this terminal session into a how-to guide in Markdown.
$(cat /tmp/session-clean.txt)
Goal of the session: nginx as a TLS reverse proxy for an app on port 3000.
Audience: a developer comfortable in a shell who has never used this server.
System: Debian 12.
Rules:
- Use only commands that appear in the session, copied exactly.
- Drop exploration (ls, cat, less, typos, failed commands) unless the reader
needs it to check state.
- If a command was retried with changes, keep the final version and add one
sentence on what the earlier attempt got wrong.
- One command per line in fenced code blocks, with no prompt characters.
- After each step, say how to confirm it worked, using output from the session.
- If a step is clearly needed but absent, write MISSING: <what> instead of
supplying a command."
$ ollama run llama3.2 "From the same session, write the Reference section of a
README: a table of every file created or edited, every service touched, and
every port opened. Use only names that appear in the session."
State the goal yourself β the model cannot tell from commands alone which ones were the point and which were detours. One sentence of intent changes what it keeps.
MISSING beats invented β a gap in your session is real information. A model that fills it with a plausible apt install hides the gap from you.
Keep Only What Worked, Then Verify
The defect to look for is a command in the document that never appeared in your session β invented, or quietly "improved". That check is mechanical:
# Commands in the doc's code blocks that you never actually ran
# (compare against the history extract, not a script log full of output)
$ awk '/^```/ { f = !f; next } f' howto.md | sed 's/^ *//' | sort -u > /tmp/doc-cmds.txt
$ sed 's/^ *//' /tmp/session-clean.txt | sort -u > /tmp/ran-cmds.txt
$ comm -23 /tmp/doc-cmds.txt /tmp/ran-cmds.txt
# Then follow the doc literally on something disposable
$ docker run --rm -it debian:12 bash
# (use a throwaway VM instead if the steps need systemd)
Every comm line needs an explanation β an added sudo or a reflowed line is fine; a new flag or package is not until you have checked it. Our command verification guide covers checking flags against --help and man pages.
Commands the doc dropped β run comm -13 the other way round and skim the result. Sometimes the model discarded a step that looked like noise but was not.
Fresh environment, literal reading β you cannot test your own how-to; you know too much. Follow it on a clean container or VM, or better, hand it to the person it was written for.
Scripts deserve review too β if the doc ends with a generated install script, review it like any other β see reviewing AI-generated shell scripts.
Date it and keep it in git β a "tested on Debian 12, 2026-10-01" line tells the next reader how far to trust it, and git shows when it last changed.
Frequently Asked Questions
How do I add timestamps to bash history?
Set HISTTIMEFORMAT in ~/.bashrc, for example HISTTIMEFORMAT='%F %T '. Bash then writes an epoch timestamp line before each command in the history file and shows formatted times in the history command. Commands saved before you set it have no timestamp.
Why is my bash history missing commands from other terminals?
By default each shell writes its history only on exit and overwrites the file. Enable shopt -s histappend and add history -a to PROMPT_COMMAND so every command is appended to the file as it runs.
Is it safe to paste bash history into an LLM?
Not without scrubbing. History routinely contains passwords, API tokens, URLs with embedded credentials and internal hostnames. Grep for common secret patterns, redact what you find, read the result yourself, and prefer a local model for anything from production.
How do I keep a command out of bash history?
With HISTCONTROL set to ignorespace or ignoreboth, start the command with a space. For secrets it is better to avoid typing them as arguments at all, for example by reading them with read -rs, because arguments are also visible in ps while the command runs.
How do I check an AI-written how-to against what I actually ran?
Extract the commands from the document's code blocks, sort both lists, and use comm -23 to show commands that appear in the document but not in your session. Explain or remove every one, then follow the document literally on a clean machine.