Skip to content

Print progress lines on stderr - #18

Merged
JacobStephens2 merged 3 commits into
mainfrom
issue-10
Sep 25, 2026
Merged

JacobStephens2 merged 3 commits into
mainfrom
issue-10

Conversation

@JacobStephens2

@JacobStephens2 JacobStephens2 commented Sep 25, 2026 •

Copy link
Copy Markdown
Owner

Summary

While a Run is going, stderr now shows its progress, and stdout still carries only the PR URL.

 run
+  "thirdshift: creating worktree … on issue-7 from origin/main"
   Worktree::create_fresh
+  "thirdshift: logging the session to ~/.thirdshift/logs/…-implement.jsonl"
   session::run("implement", …)
-    claude stdout → log file
+    claude stdout → follow(): each line → log file + Progress::condense → stderr
+    "thirdshift: implement: session ended after 5m 32s: 34 turns, $1.82 at API prices"
+  "thirdshift: pushing issue-7"
   git push
+  "thirdshift: checking the PR"
   gh pr view
+  "thirdshift: merging origin/main into issue-7"
   Worktree::merge_base_branch
   if conflicted:
+    "thirdshift: merging origin/main conflicted; starting a Repair"
+    "thirdshift: logging the session to ~/.thirdshift/logs/…-repair-1.jsonl"
     session::run("repair-1", …)
+  "thirdshift: pushing issue-7"
   Worktree::push
+  "thirdshift: cleaning up the worktree and local branch issue-7"

src/progress.rs holds progress::step for thirdshift's own steps, and Progress, a pure condenser that turns one stream line into zero or more lines:

system/init (first only)  → session started          (later inits: resumptions, skipped)
tool_use Skill            → skill thirdshift:code-review
tool_use Bash git commit  → commit    / git push → push / both → commit and push
tool_use Bash other       → $ <first line, ≤100 chars>
tool_use Read/Edit/…      → Edit src/run.rs          (path relative to the session cwd)
result                    → nothing; totals kept, the last one reported when claude exits
anything else / malformed → skipped

This follows the issue comment. A result event doesn't end the session. total_cost_usd is taken from the last result, not summed. Only the first init prints "session started". "At API prices" appears only when apiKeySource is "none" (a claude.ai subscription).

Seams tested:

  • the Progress interface (condense and summary), with unit tests in src/progress.rs
  • the thirdshift binary's stderr and stdout, driven by the fake claude emitting stream-json in tests/progress_lines.rs

Evidence

  • Before: cargo test --test progress_lines: 3 failed. stderr: [], and "missing "thirdshift: implement: session started"".
    After: cargo test: 14 unit + 8 fresh-run + 5 progress-lines tests pass. After merging main: all 45 tests pass, including mergeable and preflight. clippy and fmt are clean.
prints_one_line_per_notable_session_event                     skill / Read src/lib.rs / commit / push
reports_the_last_result_once_the_session_exits_even_after_several
  results(10, $0.50), init, results(34, $1.82) → 1 "session started", 1 "session ended … 34 turns, $1.82"
prints_a_line_for_each_of_its_own_steps                       in order
skips_unknown_and_malformed_stream_events_and_keeps_stdout_to_the_pr_url
still_logs_the_whole_stream

Merge Danger

Door: two-way

The change only adds stderr output. The log file still gets every byte of the stream.

Blast Radius: stderr

Anything that parsed thirdshift's stderr now sees more lines. claude stdout is now read through a pipe rather than written straight to the log file. If following the stream fails, claude is killed and waited on before the error is returned.

Unaddressed findings

Standards

  • session::run prints instead of returning results: the formatting logic is in the pure Progress, which is unit-tested. What remains in session::run is the I/O glue.
  • step and Progress share a module: they are the two halves of "progress on stderr". Splitting them would add a module and no depth.
  • The kind "implement" is passed to both log_path and run; session::run has five positional params: the Repair (Keep the PR mergeable: merge the Base branch and conflict Repair #17) now shares the run_session closure in run.rs, which passes kind through, so the call sites stay short.
  • tool_use test helper is duplicated in unit and integration tests: unit tests can't reach tests/support, and the copy is small.

Spec

  • No line for waiting for CI: that step doesn't exist yet. It should print its line when it is built. Merging the Base branch and starting a Repair came in from main (Keep the PR mergeable: merge the Base branch and conflict Repair #17) and print their lines.
  • Extra "logging the session to …" step, the elapsed time, and relative paths and details on tool lines: all are small and help you follow a 10–40 minute run. The log path is the only way to find the full stream.
  • claude's own stderr passes through without the thirdshift: prefix: that is claude's error output. Showing it raw is the most useful thing when a session fails, and stdout is unaffected.

Closes #10

🤖 Generated with Claude Code

JacobStephens2 and others added 3 commits September 25, 2026 04:39
Condense each session's stream-json output into one short stderr line per
notable event (session start, skills, tool calls, commits, pushes), and print
thirdshift's own steps. A result event doesn't end the session: the summary
line comes when the process exits and reports the last result's totals.
Unknown and malformed stream lines are skipped. stdout stays the PR URL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- One stderr line per tool call, so a message with many calls can't make
  one unbounded line.
- Mark the cost as "at API prices" only on a claude.ai subscription
  (apiKeySource "none"); with an API key it is a real charge.
- Recognise commits and pushes as git commands, not as substrings, so
  `grep "git push"` or `git commit-tree` isn't reported as one.
- Always wait for claude, killing it first, if following its stream fails.
- Route every "thirdshift: " line through progress::step, rename
  Progress::line to condense, and print the end-of-session line once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolve src/main.rs and src/run.rs against the pre-flight checks (#15/#16)
and the Base branch merge and conflict Repair (#17). Keep main's run
structure and carry the progress lines into it: the session log line
moves into run_session, the push line into Worktree::push, and the newly
built steps (merging the Base branch, starting a Repair) print their
lines as issue #10 asks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@JacobStephens2
JacobStephens2 merged commit d613d97 into main Sep 25, 2026
1 check passed
@JacobStephens2
JacobStephens2 deleted the issue-10 branch September 25, 2026 11:22
JacobStephens2 added a commit that referenced this pull request Sep 25, 2026
Brings in the progress lines (#18) on top of the Failed run path.

- session.rs: the stream is followed on its own thread, so the main
  thread can still poll for an interrupt and stop claude's process
  group. A follow error kills the group, as main's child.kill() did.
- run.rs: keeps the Failed run structure and adds main's progress steps;
  session::run now takes the session kind.
- main.rs, failed_run.rs: stderr lines go through progress::step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

Progress lines on stderr

1 participant