Skip to content

Command logs: each command keeps a file of what it printed, in a log tree under logs.dir - #319

Merged
JacobStephens2 merged 3 commits into
mainfrom
issue-259
Oct 3, 2026
Merged

JacobStephens2 merged 3 commits into
mainfrom
issue-259

Conversation

@JacobStephens2

Copy link
Copy Markdown
Owner

Summary

Every Run, Spec run, Architect run and Pickup run now keeps a Command log under logs.dir. It holds what the command printed on stderr and stdout, in the order printed. Session logs move into sessions/, and all of a command's logs share one local-time stamp.

 ~/.thirdshift/logs/
-├── <owner>-<repo>-issue-<n>-20261003T160000Z-implement.jsonl
+├── sessions/   <owner>-<repo>-issue-<n>-20261003T120000-0400-implement.jsonl
+└── commands/
+    ├── architect/   <owner>-<repo>-<stamp>.log
+    ├── pickup/      <owner>-<repo>-issue-<n>-<stamp>.log
+    └── issue/       <owner>-<repo>-issue-<n>-<stamp>.log
main
  command_log::begin("Pickup run starting")   # makes the stamp, holds lines, prints the dated first line
  pickup::run → takes #22
  command_log::keep(commands/pickup/…)        # writes the held lines, then "logging this command to …"
  run_to_end
    child Run (Ticket / Base fix) --stamp <stamp>   # writes no Command log; its relayed lines reach this one
progress::step / run_ending URLs → command_log::eprint / print   # terminal and file under one lock
  • Dated first line: each command's first line names the date and offset, e.g. thirdshift: 12:00:01 Pickup run starting, 2026-10-03 -0400.
  • Failed run ending: it ends with session log: … and then command log: ….
  • Run notification: it gains a Command log: line.
  • Write failures: a Command log that can't be written gives one warning: line, and the outcome is unchanged.
  • Skips: a Pickup run or Architect run that is skipped before doing any work keeps no Command log.
  • Docs: the help text, the logs.dir comment written by setup, the README's Logs section and the site's Failed run example are updated.

Evidence

  • Before: no file held a command's output. Session logs were flat, with UTC …Z stamps, and a Ticket's Run or a Base fix made a stamp of its own.
    After: tests/command_logs.rs has 11 tests and all pass, with TZ=XYZ+4 so stamps end in -0400. They cover:
    • a hand-started Run: the file equals stderr then stdout, and its Session log shares the stamp
    • a Spec run: #21: and #22: lines are in the Spec run's file, its Tickets keep none, and their Session logs share the stamp
    • a Base fix: #8: lines are in the Run's file
    • a Pickup run: the "passed over" lines come before logging this command to
    • a skipped Pickup run and a skipped Architect run: no logs/ folder
    • an Architect run that dispatches a run
    • a Failed run's last two lines
    • the notification's Command log: line
    • an unwritable commands/: exactly one warning:, exit 0, stdout unchanged
  • Full suite: cargo test passes on all 31 targets, and cargo clippy --all-targets is clean. Existing tests changed in three ways:
    • Session log paths now include sessions/.
    • Exact-stderr checks skip the dated first line, via support::after_start.
    • "Last line" checks of Failed runs allow for the trailing command log: line, via support::before_command_log.

Merge Danger

Door: two-way

Nothing is moved or deleted. Existing flat Session logs stay where they are, and new ones go to sessions/.

Blast Radius: output

  • stderr shape: every Run, Spec run, Architect run and Pickup run now has an extra first line. A Failed run has an extra last line.
  • Anything parsing stderr: a script that reads the last line of a Failed run as the session log needs to look one line earlier.
  • Child Runs: they now take a hidden --stamp argument. Parent and child are always the same binary, so the two can't disagree about it.

Unaddressed findings

Standards

  • Process-global state in command_log (static LOG and STARTED), and Logs::path reading the stamp from it. Skipped: there is one stamp and one Command log per process, by the spec's design ("made once and given to everything under it"). Passing them through BaseFix, spec_run and child_run would widen several signatures for no behaviour gain.
  • command_log interface breadth. It has three of_* path functions plus keep. Skipped: folding them into keep would only move the three-way choice around. The path builders keep naming separate from keeping the file.
  • Duplicated name format (<owner>-<repo>-issue-<n>) between command_log::issue_name and Logs::of_run. Skipped: one short format string, owned by two different log kinds. Sharing it would couple Session log names to Command log names.
  • Repeated child check in main (begin / begin_child, then keep only if not a child). Skipped: the two checks sit on either side of loading the User config, which supplies logs.dir.
  • RunArgs.stamp travels with child. It could live in Kind or be its own type. Skipped: Kind is the child Run's role and Base branch, while the stamp is about logging and is the same for every kind. A newtype would wrap one string passed through two places.
  • Body fixtures in notification.rs each gained command_log: None. Skipped: this follows the existing pattern there.

Spec

  • Setup offer lines not in the Command log. These are the questions asked on a terminal with no User config. Skipped: they are not written through the progress module, which is the source the spec names ("every line thirdshift writes to stderr through the progress module"). They are also interactive only, never part of a scheduled pass.
  • A Run whose User config is invalid keeps no Command log. Skipped: logs.dir isn't known until the User config loads, so there is nowhere to put the file. The failure still prints to stderr and to any crontab catch-all log.

Closes #259

🤖 Generated with Claude Code

JacobStephens2 and others added 3 commits October 3, 2026 02:35
…tree under logs.dir (#259)

Every Run, Spec run, Architect run and Pickup run writes its stderr and
stdout, in order, to commands/<issue|pickup|architect>/ under logs.dir,
and Session logs move to sessions/. Stamps are local time with the UTC
offset, made once per command and passed to child Runs, so a command and
all its Session logs share one. A command's first line is dated, a Failed
run ends naming its Command log, and the Run notification carries a
Command log: line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ups (#259)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@JacobStephens2
JacobStephens2 merged commit 4db1d74 into main Oct 3, 2026
13 checks passed
@JacobStephens2
JacobStephens2 deleted the issue-259 branch October 3, 2026 06:50
@JacobStephens2 JacobStephens2 mentioned this pull request Oct 3, 2026
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.

Command logs: each command keeps a file of what it printed, in a log tree under logs.dir

1 participant