Skip to content
Back to skills

Pbar

ASecurity

REQUIRED, unprompted, right after you start any background job (Bash run_in_background, nohup, a long script, a batch or pipeline) you expect to run 5+ minutes — do not wait to be asked. Builds a live terminal progress display and hands back its paste-ready command in a copyable code block, re-printed whenever the user might need to restart it. Also use when the user asks for status on a running job.

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 6, 2026
ai-agentsrustgobashawsapidatabase

Works with

  • terminal
  • cli
  • api

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned October 6, 2026

npx -y skills add tyler-berggren/tb-claude-kit --skill pbar --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Pbar?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Pbar
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tyler-berggren-pbar/badge)](https://www.skillsdirectory.com/skills/tyler-berggren-pbar)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: pbar
description: REQUIRED, unprompted, right after you start any background job (Bash run_in_background, nohup, a long script, a batch or pipeline) you expect to run 5+ minutes — do not wait to be asked. Builds a live terminal progress display and hands back its paste-ready command in a copyable code block, re-printed whenever the user might need to restart it. Also use when the user asks for status on a running job.
argument-hint: "[what to watch — a log file, a job name, or nothing to infer from context]"
---

# pbar — a progress bar for a background job

Long jobs make bad conversation. Without a display the user has to ask "status?" every few
minutes and you have to burn a tool call re-deriving the same numbers. This builds them a
self-contained watcher script and gives them one line to paste.

**The deliverable is the pasted line, not the script.** Finish by printing a single command the
user can copy into any terminal, from any directory. If they have to `cd` first, or edit a path,
or remember a flag, this skill has failed.

## When this is mandatory

**Every time you start a job you expect to run 5 minutes or more, run this skill immediately after
launching it — in the same turn, without being asked.** It does not matter whether the job is a
`run_in_background` Bash call, a `nohup … &`, a script that loops over an API, or a batch you
kicked off some other way. The user should never have to ask for a progress bar, and never have to
ask "status?".

- **Unsure whether it takes 5 minutes? Build it anyway.** An unneeded bar costs one tool call; a
  missing one costs the user a string of status questions.
- **Skip it only when you are confident** the job finishes in under 5 minutes.
- A PostToolUse hook reminds you after each `run_in_background` Bash call. The reminder is only a
  nudge — the rule applies to every long job, however it was started.

## What makes a progress bar possible

A bar needs two things: a **quantity that grows** and a **total it grows toward**. Most of the work
is finding an honest pair. In rough order of preference:

| Signal | Read it with | Good when |
|---|---|---|
| Rows in an output file | `wc -l < f` | The job writes records as it goes |
| Bytes in an output file | `stat -f%z f` (macOS) / `stat -c%s f` (Linux) | Output is not line-oriented |
| A counter the job prints | `tail -1 log \| grep -oE '…'` | The job already reports progress |
| Rows in a database table | a `count(*)` query | The job writes straight to a database |
| Completed items in a directory | `ls dir \| wc -l` | The job writes one file per unit |

**If there is no honest total, say so and do not invent one.** A bar against a guessed
denominator is worse than a plain counter, because it looks authoritative. Fall back to showing
the count, the rate, and elapsed time.

## Procedure

### 1. Find out what is actually running

Do not guess. Establish, with commands:

```bash
# What is running, and what did it write?
pgrep -fl '<job pattern>'
ls -la <output dir>
tail -5 <log file>
```

You need: the **log path**, the **output paths**, the **process pattern** that proves liveness, and
the **totals**. Get totals from the job's own output where possible (a "N features" line, a source
API's count endpoint, a manifest) rather than from memory.

### 2. Copy the template into the PROJECT, not the skill folder

> **`.claude/skills/pbar/` is a SYMLINK to the shared kit checkout.** Kit skills install by
> symlink, so a watcher written there lands in the kit repo and appears in every other project
> that installs it. Generated watchers must go somewhere project-local. `install.sh` only ever
> symlinks paths under `.claude/skills/`, so `.claude/pbar/` is safe.

```bash
mkdir -p .claude/pbar
cp .claude/skills/pbar/template.sh .claude/pbar/watch-<job-name>.sh
```

Then edit the settings block at the top — `JOB`, `LOG`, `DIR`, `PATTERN`, `EXT`, `NAMES`, `TOTAL`.
A second job adds a second `watch-*.sh` rather than overwriting the first.

The **paste line is the absolute path** to that file. Resolve it once and use it verbatim:

```bash
printf '%s/.claude/pbar/watch-<job-name>.sh\n' "$(pwd)"
```

Project-local file, absolute invocation — that is what lets it run from any directory without a
`cd`.

The script must:

- **Use absolute paths INSIDE the script too.** It lives in the project but will be invoked from
  an unknown directory, so every log and output path it reads must be absolute. Do not rely on
  `$(dirname "$0")` unless you resolve it to an absolute path first.
- **Be strictly read-only.** No writes, no kills, no `set -e` that could exit on a transient
  `wc` failure. The user must be able to Ctrl-C it, close the terminal, or run five copies,
  without touching the job.
- **Show liveness separately from progress.** This is the one that bites: a stalled job and a
  finishing job produce identical bars. Print whether the producer process still exists on every
  redraw. A bar at 47% with `producer: NOT RUNNING` is the single most useful line the display can
  show, and a bar alone can never say it.
- **Exit on its own** when everything is complete, so the terminal comes back.
- **State totals as assumptions.** If they were probed once and hardcoded, say so in a comment —
  a shifted total makes the percentage wrong while the raw count stays honest.

### 3. The rendering details that go wrong

**Bars: slice fixed strings, never loop `seq`.**

```bash
FILL='##############################'   # 30 chars
BLANK='                              '   # 30 chars
bar() { local w=30 f=$(( $1 * 30 / 100 )); printf '[%s%s]' "${FILL:0:$f}" "${BLANK:0:$((w-f))}"; }
```

`printf '%0.s#' $(seq 1 0)` prints **one** `#`, not zero — with an empty argument list printf still
runs its format once. An empty bar renders as `[#…]`. Slicing cannot do that.

**Redraw in place, and clear properly.** `clear` needs a `TERM`; when in doubt use
`printf '\033[H\033[2J'`. Redraw every 2–5s for local files, 15–30s if a poll costs a network
round-trip or a database query.

**Percentages must clamp.** `p=$((n*100/t)); [ $p -gt 100 ] && p=100`. A total probed slightly low
otherwise renders a bar wider than its brackets and wrecks the layout.

**Rate and ETA from observed throughput**, not from a fixed guess — record the first sample and
divide. Show `--` until there is enough signal, rather than a confident wrong number.

**zsh does not word-split unquoted expansions.** Iterate with `echo "$VAR" | while IFS= read -r`,
never `for x in $VAR`.

### 4. Make it executable, check one frame, and hand over the line

```bash
chmod +x .claude/pbar/watch-<job-name>.sh
PBAR_ONCE=1 .claude/pbar/watch-<job-name>.sh    # draws ONE frame and exits
```

**Read that frame before handing anything over.** A display that shows nothing — a zero count
for a job that has clearly started, a missing log, `producer: NOT RUNNING` for a live job — is
worse than no display: the user watches it, sees nothing move, and stops trusting the next one.
The usual causes are a log path the job never writes to (its output went to a pipe or another
file), a counted file with a different extension, or a pattern that matches nothing. Fix the
settings until the frame shows the job as it actually is, then hand over the line.

Then print the command **in its own fenced code block, as the last thing in your reply** — the
**absolute** path, so it works from any directory. Always this exact shape:

````
Watch it live (paste into any terminal):

```
/abs/path/to/project/.claude/pbar/watch-<job-name>.sh
```
````

The code block is the point: most terminals and editors give it a one-click copy, and it cannot be
mistaken for prose. **Never** put the command inline in a sentence, inside backticks mid-paragraph,
or in a table. The block holds that one line only — no `$ ` prompt, no `cd`, no comment, no second
command.

Say in one sentence what it shows and that stopping it cannot affect the job. Do not print the
script's source unless asked — they wanted a display, not a code review.

### 4b. Re-print the block whenever the user might need to restart it

The watcher runs in a terminal the user controls. Terminals get closed, tabs get killed, laptops
sleep, and the watcher exits on its own at COMPLETE. Scrolling back through a long session to find
the line is exactly the friction this skill exists to remove. **Re-print the same code block, as
the last thing in your reply, whenever the job is still relevant and any of these is true:**

- You give **any status update** on the job — a Monitor event, a background-task notification,
  an answer to "how's it going?".
- The job was **restarted, resumed, or relaunched** (after a crash, a fix, a rate limit). If its
  outputs, log path or totals changed, rebuild the watcher first, then print the block.
- The job **crashed or stalled** — the user will want to watch the retry.
- The user says the display **stopped, froze, looks wrong, or they closed it**.
- The session **resumed or was compacted** while the job is still running.
- You are **wrapping up a turn** while the job is still running.

When in doubt, print it. A repeated three-line block is cheap; hunting for a lost command is not.
The watcher itself also shows its own restart command at the bottom of every redraw and again on
Ctrl-C, so the line is on screen even when the conversation is not.

### 5. Arm a notification too

A display only helps while someone is looking at it. Also set a watch that fires once, on
**either** outcome:

```
Monitor(persistent: true) with a poll loop that emits one line and exits when the job
completes OR when the producer process disappears.
```

Covering only success is the classic mistake — the monitor then stays silent through a crash, and
silence is indistinguishable from "still working". Ask yourself: *if this job died right now, would
anything fire?*

**A poll loop must not match itself.** `while pgrep -f 'my-job' …` written into a Bash command or a
Monitor has `my-job` in its own command line, so `pgrep -f` finds the loop and the loop never
exits — the waiter outlives the job by hours. Wait on the job's PID (`while kill -0 "$PID"`), on a
file the job writes when it ends (`cmd; echo $? > /abs/done`), or write the pattern as
`'[m]y-job'`: the regex still matches the job, but no longer matches the literal text in the loop.

## Template

`template.sh` in this folder is the working starting point — copy it as described in step 2 and
edit its settings block. It already handles the bar rendering, clamping, observed-rate ETA, the
liveness line and self-exit. The listing below is that same script, for reference when adapting it
to a job whose progress is not "rows in files" (a database count, a directory of finished items, a
counter the job prints).

Multi-stage jobs get one row per stage; single-stage jobs get one bar plus rate and ETA.

```bash
#!/usr/bin/env bash
# Live progress for <job>. Read-only: stopping this cannot affect the job.
# Totals probed <date> — a shifted total skews the %, the raw count stays honest.
LOG=/abs/path/to/job.log
DIR=/abs/path/to/output
PATTERN='<pgrep pattern proving the producer is alive>'
declare -a NAMES=(stage-a stage-b)
declare -a TOTAL=(1000 2000)

FILL='##############################'
BLANK='                              '
bar() { local w=30 f=$(( $1 * 30 / 100 )); printf '[%s%s]' "${FILL:0:$f}" "${BLANK:0:$((w-f))}"; }
SELF="$(cd "$(dirname "$0")" && pwd)/$(basename "$0")"   # shown as the restart command
trap 'printf "\n  restart this display:\n  %s\n" "$SELF"; exit 0' INT TERM

start=$(date +%s); first=-1
while true; do
  printf '\033[H\033[2J'
  echo "<job> — $(date '+%H:%M:%S')"; echo
  total_now=0; grand=0; done_all=1
  for i in "${!NAMES[@]}"; do
    f="$DIR/${NAMES[$i]}.out"; t=${TOTAL[$i]}
    n=$([ -f "$f" ] && wc -l < "$f" | tr -d ' ' || echo 0)
    p=$(( n * 100 / t )); [ $p -gt 100 ] && p=100
    [ "$n" -lt "$t" ] && done_all=0
    total_now=$(( total_now + n )); grand=$(( grand + t ))
    printf "  %-14s %s %3d%%  %9d / %d\n" "${NAMES[$i]}" "$(bar $p)" "$p" "$n" "$t"
  done
  [ $first -lt 0 ] && first=$total_now
  el=$(( $(date +%s) - start ))
  echo
  if [ $el -gt 10 ] && [ $total_now -gt $first ]; then
    rate=$(( (total_now - first) / el ))
    [ $rate -gt 0 ] && echo "  ~$(( (grand - total_now) / rate / 60 )) min left  (${rate}/s)"
  fi
  # Liveness is NOT progress — a stalled bar looks exactly like a finishing one.
  if pgrep -f "$PATTERN" >/dev/null; then echo "  producer: running"; else echo "  producer: NOT RUNNING"; fi
  tail -1 "$LOG" 2>/dev/null | sed 's/^/  last: /'
  echo; echo "  restart: $SELF"
  [ $done_all -eq 1 ] && { echo; echo "  COMPLETE"; break; }
  sleep 5
done
```

## Rules

- **No confirmation needed** — build it and hand over the line.
- **Read-only, always.** The watcher may never write, kill, or lock anything the job touches.
- **Never fabricate a total.** No honest denominator means no bar — show count, rate and elapsed.
- **Liveness on every redraw.** Without it the display cannot distinguish stalled from finishing.
- **Check one frame first** (`PBAR_ONCE=1`). Never hand over a display you have not seen show the
  job's real numbers.
- **No self-matching waiters.** A poll that `pgrep -f`s a pattern in its own command line never
  exits; wait on a PID or a done-file, or use the `[p]attern` form.
- **Mandatory for 5+ minute jobs.** Build it right after launch, unprompted.
- **One line at the end, in its own fenced code block.** Absolute path, no `cd`, no arguments, no
  editing, never inline in prose.
- **Re-print the block** on every status update, restart, crash, resume, or turn that ends with the
  job still running.

---

## Project overrides

If `.claude/kit.json` has a `rules."pbar"` entry, read it and apply it as an additional
instruction for this skill. Absent file or key means no overrides — that is the normal case.

```bash
jq -r '.rules."pbar" // empty' .claude/kit.json 2>/dev/null
```

Files in this skill

  • SKILL.md13.7 KB
  • template.sh4.9 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…