Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -609,7 +609,7 @@ The Credentials are read only when `RESEND_API_KEY` is unset or empty, and only
A Run that asks for a notification, by the flag or by `email.always`, makes the same checks as `email-test` before any other work: an address is known, and a key is found, in `RESEND_API_KEY` or else the Credentials. If either fails, or the Credentials are broken, the Run stops, exits `1` naming what's wrong (with no key, the message above listing every way to give one), and sends nothing. A Run that asks for no notification never reads the Credentials, so a broken file can't stop it, and `update`, `version` and `help` never read it either. Once they pass, every way the Run ends sends exactly one notification, after its outcome is final and its cleanup done: ready for review, merged, a [Failed run](#failed-runs) (including a later preflight failure such as an origin mismatch), or interrupted by Ctrl-C, SIGTERM or a closed terminal.

- **Subject**: `[thirdshift] <owner>/<repo>#<n> <issue title>: <outcome>`, where the outcome is `ready for review`, `merged`, `failed` or `interrupted`. The title is left out if it can't be read from GitHub.
- **Body**, plain text: a `Result:` line with the same outcome as the subject, so it is readable even when a long issue title hides the end of the subject; immediately followed by one `Issue:` line with the full original Issue URL, including after failure or interruption with no pull request, and a `Title:` line with the issue title when it was read; then the pull request URL (if any), the failure cause (if failed), after an Inherited failure with no Base fix taken its [`Base check:`, `Retry with:` and `Or set:` lines](#an-inherited-failure-links-the-base-branchs-checks), the session log path (if any), the [Command log](#logs) path as a `Command log:` line, a `Sent by: thirdshift` line, the hostname and how long the Run took. The body tells everything the subject does, so copying the body alone is enough.
- **Body**, plain text: a `Result:` line with the same outcome as the subject, so it is readable even when a long issue title hides the end of the subject; immediately followed by one `Issue:` line with the full original Issue URL, including after failure or interruption with no pull request, and a `Title:` line with the issue title when it was read; then the pull request URL (if any), the failure cause (if failed), after an Inherited failure with no Base fix taken its [`Base check:`, `Retry with:` and `Or set:` lines](#an-inherited-failure-links-the-base-branchs-checks), the session log path (if any), the same one stderr gives, the [Command log](#logs) path as a `Command log:` line, a `Sent by: thirdshift` line, the hostname and how long the Run took. The body tells everything the subject does, so copying the body alone is enough.

The email's `Built with` line names the command's configured Harness, Model and Effort and is marked `(configured)`. A `Session models` section lists sessions whose Harness was launched, including failed or interrupted sessions, Resumes and child Runs belonging to that Command. Models evidenced by the Harness's output or retained records are marked `(reported)`, including model switches and delegated Claude answers. When the Harness supplies no model evidence, the requested Model is labelled `(requested; actual model not reported)`, or its default is labelled `Harness default (actual model not reported)`. A listed session proves the Harness was launched; an unconfirmed request does not prove that Model executed. Effort is the session's configured Effort; it does not describe a delegated answer's Effort. Attribution records (`*.models.json`) are kept beside the Session logs; a failure to write or read them is a warning and never changes the Run's outcome.

Expand Down Expand Up @@ -679,7 +679,7 @@ A Resume is logged as its session's kind plus `-resume`, e.g. `implement-resume.

A **Command log** is everything one command printed, stderr and stdout in the order printed, while the terminal still shows all of it. Its folder is the command typed: `thirdshift <Issue URL>`, a Run or a Spec run, in `issue/`, `thirdshift pickup` in `pickup/`, named for the issue it took, and `thirdshift architect` in `architect/`. A Spec run's covers its Tickets' Runs, a Run's covers its [Base fix](#base-fix), and a Pass's covers the Spec run or Run it dispatched: none of those keeps one of its own. So `ls -t ~/.thirdshift/logs/acme/widgets/commands/pickup/*.log | head` lists the recent passes on acme/widgets that took an issue. A command keeps its Command log once it starts work: a Pass skipped before doing any work keeps none, nor does a Run that fails before it starts work, as on the [Origin match](#what-a-run-does), and `setup`, `email-test`, `update`, `version` and `help` never keep one. Lines printed before the Command log's name is known, such as a Pickup run's lines on the issues it [passed over](#why-an-issue-was-passed-over) before it took one, are written first, and once it is created a progress line says `logging this command to <path>`. A Command log that can't be written produces one `warning:` line on stderr. A failure confined to the Command log leaves the outcome, stdout and exit code unchanged; if the shared folder also prevents writing a Session log, the Run fails before starting that session.

The stamp is the local time the command started, with its UTC offset, as in `20261003T120000-0400`, in the machine's time zone, or `TZ`'s if set, so it agrees with `date` and `ls -l`. A command's Command log and all of its Session logs, its Tickets' Runs' and its Base fix's included, share that one stamp, so they sort together and each can be found from the other. When a Run fails, stderr ends with the path of its most recent Session log, the place to start looking, then that of its Command log.
The stamp is the local time the command started, with its UTC offset, as in `20261003T120000-0400`, in the machine's time zone, or `TZ`'s if set, so it agrees with `date` and `ls -l`. A command's Command log and all of its Session logs, its Tickets' Runs' and its Base fix's included, share that one stamp, so they sort together and each can be found from the other. When a Run fails, stderr ends with the path of the Session log of the session its cause came from, the place to start looking, then that of its Command log. A failed implement session, Spec review or Repair names its own log, as does a [Security review](#security-review) whose hold is reported only after later Repairs. A cause that came from no session, such as red CI with the Repairs used up, a Declined CI fix, the upstream-move budget, a Harness check or a git or GitHub error, names the most recent Session log. A Spec run whose Tickets are not done names the Session log of the one Ticket whose Run failed, and none if several did, as no single session caused it; each failed Ticket's line gives its own. The Command log's closing `session log:` line and the [Run notification](#run-notifications) give the same path.

New Command and Session logs insert `~<token>` immediately after the stamp. The random token gives each Command its own storage even when start times agree; its Command record uses the exact Command-log stem followed by `.command.json`. Model sidecars (`.models.json`) and review reports (`.standards` and `.spec`) use the exact Session-log stem. Human Command names stay the same, and start stamps remain display and grouping facts. A matching stamp alone does not attach a tokenless Session to a new Command: a record reference or exact Model evidence must establish that ownership. Legacy logs and records remain readable without renaming. A failed Command-log writer retains its allocated owner; if allocation itself failed, a Session fails with that cause instead of choosing a shared timestamp-only filename.

Expand Down
4 changes: 2 additions & 2 deletions site/docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -561,7 +561,7 @@
<p>A Run that asks for a notification, by the flag or by <code>email.always</code>, makes the same checks as <code>email-test</code> before any other work: an address is known, and a key is found, in <code>RESEND_API_KEY</code> or else the Credentials. If either fails, or the Credentials are broken, the Run stops, exits <code>1</code> naming what&#39;s wrong (with no key, the message above listing every way to give one), and sends nothing. A Run that asks for no notification never reads the Credentials, so a broken file can&#39;t stop it, and <code>update</code>, <code>version</code> and <code>help</code> never read it either. Once they pass, every way the Run ends sends exactly one notification, after its outcome is final and its cleanup done: ready for review, merged, a <a href="#failed-runs">Failed run</a> (including a later preflight failure such as an origin mismatch), or interrupted by Ctrl-C, SIGTERM or a closed terminal.</p>
<ul>
<li><strong>Subject</strong>: <code>[thirdshift] &lt;owner&gt;/&lt;repo&gt;#&lt;n&gt; &lt;issue title&gt;: &lt;outcome&gt;</code>, where the outcome is <code>ready for review</code>, <code>merged</code>, <code>failed</code> or <code>interrupted</code>. The title is left out if it can&#39;t be read from GitHub.</li>
<li><strong>Body</strong>, plain text: a <code>Result:</code> line with the same outcome as the subject, so it is readable even when a long issue title hides the end of the subject; immediately followed by one <code>Issue:</code> line with the full original Issue URL, including after failure or interruption with no pull request, and a <code>Title:</code> line with the issue title when it was read; then the pull request URL (if any), the failure cause (if failed), after an Inherited failure with no Base fix taken its <a href="#an-inherited-failure-links-the-base-branchs-checks"><code>Base check:</code>, <code>Retry with:</code> and <code>Or set:</code> lines</a>, the session log path (if any), the <a href="#logs">Command log</a> path as a <code>Command log:</code> line, a <code>Sent by: thirdshift</code> line, the hostname and how long the Run took. The body tells everything the subject does, so copying the body alone is enough.</li>
<li><strong>Body</strong>, plain text: a <code>Result:</code> line with the same outcome as the subject, so it is readable even when a long issue title hides the end of the subject; immediately followed by one <code>Issue:</code> line with the full original Issue URL, including after failure or interruption with no pull request, and a <code>Title:</code> line with the issue title when it was read; then the pull request URL (if any), the failure cause (if failed), after an Inherited failure with no Base fix taken its <a href="#an-inherited-failure-links-the-base-branchs-checks"><code>Base check:</code>, <code>Retry with:</code> and <code>Or set:</code> lines</a>, the session log path (if any), the same one stderr gives, the <a href="#logs">Command log</a> path as a <code>Command log:</code> line, a <code>Sent by: thirdshift</code> line, the hostname and how long the Run took. The body tells everything the subject does, so copying the body alone is enough.</li>
</ul>
<p>The email&#39;s <code>Built with</code> line names the command&#39;s configured Harness, Model and Effort and is marked <code>(configured)</code>. A <code>Session models</code> section lists sessions whose Harness was launched, including failed or interrupted sessions, Resumes and child Runs belonging to that Command. Models evidenced by the Harness&#39;s output or retained records are marked <code>(reported)</code>, including model switches and delegated Claude answers. When the Harness supplies no model evidence, the requested Model is labelled <code>(requested; actual model not reported)</code>, or its default is labelled <code>Harness default (actual model not reported)</code>. A listed session proves the Harness was launched; an unconfirmed request does not prove that Model executed. Effort is the session&#39;s configured Effort; it does not describe a delegated answer&#39;s Effort. Attribution records (<code>*.models.json</code>) are kept beside the Session logs; a failure to write or read them is a warning and never changes the Run&#39;s outcome.</p>
<p>A <a href="#spec-runs">Spec run</a> sends at most one notification for the whole Spec, under the same rules, with its checks made once before any Ticket starts. Its subject names the Spec, its <code>Issue:</code> line links the parent Spec rather than a Ticket or pull request, its outcome is the Spec run&#39;s, and its body, after what a Run&#39;s holds (the Spec PR, if any), lists each Ticket&#39;s outcome, one line per Ticket as in the summary on stderr, such as <code>#21 landed with https://github.com/acme/widgets/pull/1</code> or <code>#22 blocked by #21</code>. The Ticket Runs inside it never send a notification of their own, whatever the User config says.</p>
Expand Down Expand Up @@ -615,7 +615,7 @@
<p>Session logs share the owning command&#39;s folder, stamp and owner token: a Pickup run&#39;s dispatched Run logs in <code>commands/pickup/</code>, an Architect run&#39;s in <code>commands/architect/</code>, and a Security run&#39;s in <code>commands/secure/</code>. Tickets and nested Base fixes inherit that same ownership too.</p>
<p>A Resume is logged as its session&#39;s kind plus <code>-resume</code>, e.g. <code>implement-resume.jsonl</code>.</p>
<p>A <strong>Command log</strong> is everything one command printed, stderr and stdout in the order printed, while the terminal still shows all of it. Its folder is the command typed: <code>thirdshift &lt;Issue URL&gt;</code>, a Run or a Spec run, in <code>issue/</code>, <code>thirdshift pickup</code> in <code>pickup/</code>, named for the issue it took, and <code>thirdshift architect</code> in <code>architect/</code>. A Spec run&#39;s covers its Tickets&#39; Runs, a Run&#39;s covers its <a href="#base-fix">Base fix</a>, and a Pass&#39;s covers the Spec run or Run it dispatched: none of those keeps one of its own. So <code>ls -t ~/.thirdshift/logs/acme/widgets/commands/pickup/*.log | head</code> lists the recent passes on acme/widgets that took an issue. A command keeps its Command log once it starts work: a Pass skipped before doing any work keeps none, nor does a Run that fails before it starts work, as on the <a href="#what-a-run-does">Origin match</a>, and <code>setup</code>, <code>email-test</code>, <code>update</code>, <code>version</code> and <code>help</code> never keep one. Lines printed before the Command log&#39;s name is known, such as a Pickup run&#39;s lines on the issues it <a href="#why-an-issue-was-passed-over">passed over</a> before it took one, are written first, and once it is created a progress line says <code>logging this command to &lt;path&gt;</code>. A Command log that can&#39;t be written produces one <code>warning:</code> line on stderr. A failure confined to the Command log leaves the outcome, stdout and exit code unchanged; if the shared folder also prevents writing a Session log, the Run fails before starting that session.</p>
<p>The stamp is the local time the command started, with its UTC offset, as in <code>20261003T120000-0400</code>, in the machine&#39;s time zone, or <code>TZ</code>&#39;s if set, so it agrees with <code>date</code> and <code>ls -l</code>. A command&#39;s Command log and all of its Session logs, its Tickets&#39; Runs&#39; and its Base fix&#39;s included, share that one stamp, so they sort together and each can be found from the other. When a Run fails, stderr ends with the path of its most recent Session log, the place to start looking, then that of its Command log.</p>
<p>The stamp is the local time the command started, with its UTC offset, as in <code>20261003T120000-0400</code>, in the machine&#39;s time zone, or <code>TZ</code>&#39;s if set, so it agrees with <code>date</code> and <code>ls -l</code>. A command&#39;s Command log and all of its Session logs, its Tickets&#39; Runs&#39; and its Base fix&#39;s included, share that one stamp, so they sort together and each can be found from the other. When a Run fails, stderr ends with the path of the Session log of the session its cause came from, the place to start looking, then that of its Command log. A failed implement session, Spec review or Repair names its own log, as does a <a href="#security-review">Security review</a> whose hold is reported only after later Repairs. A cause that came from no session, such as red CI with the Repairs used up, a Declined CI fix, the upstream-move budget, a Harness check or a git or GitHub error, names the most recent Session log. A Spec run whose Tickets are not done names the Session log of the one Ticket whose Run failed, and none if several did, as no single session caused it; each failed Ticket&#39;s line gives its own. The Command log&#39;s closing <code>session log:</code> line and the <a href="#run-notifications">Run notification</a> give the same path.</p>
<p>New Command and Session logs insert <code>~&lt;token&gt;</code> immediately after the stamp. The random token gives each Command its own storage even when start times agree; its Command record uses the exact Command-log stem followed by <code>.command.json</code>. Model sidecars (<code>.models.json</code>) and review reports (<code>.standards</code> and <code>.spec</code>) use the exact Session-log stem. Human Command names stay the same, and start stamps remain display and grouping facts. A matching stamp alone does not attach a tokenless Session to a new Command: a record reference or exact Model evidence must establish that ownership. Legacy logs and records remain readable without renaming. A failed Command-log writer retains its allocated owner; if allocation itself failed, a Session fails with that cause instead of choosing a shared timestamp-only filename.</p>
<p>The <strong>Activity log</strong>, <code>activity.log</code>, is a short running record of what the factory did on the repository (<a href="https://github.com/JacobStephens2/thirdshift/blob/main/docs/adr/0011-thirdshift-keeps-a-per-repository-activity-log.md">ADR-0011</a>). A Run, a Spec run or a Pass writes a line when it starts work, naming its Command log, and one when it ends, with its outcome; a Run you type by hand writes them too, and the run a Pass dispatched, a Spec run&#39;s Tickets and a Base fix write none of their own. A skipped Pass writes a line only when its reason differs from the last line of its own kind, so a repository that sits idle shows one line, not one per pass, and the first skip after a pass that did work always shows: that is when the repository went idle. Every line starts with the local date and time:</p>
<pre><code>2026-10-03 02:00:01 Pickup run skipped: no Ready issue on acme/widgets
Expand Down
Loading
Loading