Subsession receipts with a no-change guard - #2

Merged
githubscum merged 2 commits into
mainfrom
feat/subsessions
Jul 22, 2026
Merged

Subsession receipts with a no-change guard#2
githubscum merged 2 commits into
mainfrom
feat/subsessions

Conversation

@githubscum

Copy link
Copy Markdown
Owner

Closes KNOWN-LIMITS item 5. Follows the merged install-readiness and session-receipt work in #1.

The problem

Claude Code fires SessionEnd more than once for the same session (on clear, on resume, on exit). The hook deduped by session id, so the first firing won and everything after it was lost. A session that got receipted and then kept working left no record of the later work.

The fix

Each firing that carries new activity appends a NEW receipt, indexed subsession 0, 1, ... n.

Receipts stay cumulative rather than deltas. Every firing carries the whole transcript, so each receipt is a complete self-contained summary that stands on its own. Receipts are summaries (digests and counts, never file contents), so the redundancy is cheap and buys back the property that any single receipt is independently meaningful.

A no-change guard prevents inflation: if the transcript has not grown since the last receipt for that session, nothing is appended.

The race this had to avoid

The subsession decision reads the chain and then appends, which is a time-of-check-to-time-of-use race. Two firings for the same session can both observe "no receipt yet" and both append subsession 0.

appendReceiptGuarded(buildPayload) runs the whole check-then-append inside the existing chain lock. The callback receives the freshly re-read tail and returns either a payload or null to skip.

Reproduced before fixing: 4 barrier-synchronized processes ingesting the same transcript produced 4 receipts instead of 1. The test was verified to fail against an unguarded check-then-append before being accepted, because a concurrency test that passes both before and after a fix is worthless.

Changes

  • src/store/index.js: new appendReceiptGuarded. Existing appendReceipt, reload, verify untouched.
  • src/parser/index.js: counts.transcriptEntries is the growth marker.
  • src/ingest/index.js: owns the subsession decision so the hook and the manual CLI share one implementation. Returns {entry, skipped, subsession, sessionId}.
  • bin/hook-session-end.js: drops its own dedupe. Every existing guarantee kept: never writes stdout, never exits non-zero, catches all failures, bounded stdin read.
  • src/views/index.js: morning-after reports total session receipts AND distinct sessions, since one session can now produce several receipts and a single number misleads. Session receipts show a subsession line when present, omitted for older receipts.
  • src/mcp/server.js: query_receipts exposes subsession.
  • KNOWN-LIMITS.md: item 5 rewritten for the new behavior.

Verification

  • 96 tests passing (88 baseline plus 8).
  • Concurrent-guard test proven to fail against the unguarded implementation and pass against the guarded one.
  • Real-transcript check: first run appends subsession 0, second run reports no new activity and appends nothing.

githubscumand others added 2 commits July 22, 2026 11:36
Closes KNOWN-LIMITS 5 (first-write-wins session receipts). Claude Code
fires SessionEnd more than once per session (clear, resume, exit), so a
session that was receipted and then kept working lost that later work.
Each qualifying firing now appends a NEW receipt indexed subsession
0, 1, ... n. Receipts stay cumulative rather than deltas: every firing
carries the whole transcript, so each receipt is a complete self-contained
summary that stands alone. Receipts are summaries (digests and counts,
never file contents), so the redundancy is cheap.
A no-change guard prevents inflation: if the transcript has not grown
since the last receipt for that session, nothing is appended.
- store: new appendReceiptGuarded(buildPayload) runs the whole
check-then-append inside the chain lock. Without it the subsession
decision is a TOCTOU: two firings both observe "no receipt yet" and both
append subsession 0. Reproduced with 4 barrier-synchronized processes,
which yielded 4 receipts instead of 1.
- parser: counts.transcriptEntries is the growth marker.
- ingest: owns the subsession decision so the hook and the manual CLI
share it. Returns {entry, skipped, subsession, sessionId}.
- hook: drops its own dedupe, reports the appended subsession or the skip.
Every existing guarantee kept (never stdout, never non-zero, catch all).
- views: morning-after reports total session receipts AND distinct
sessions, since one session can now produce several receipts and a
single number misleads. Session receipt shows the subsession line when
present, omitted for older receipts.
- MCP: query_receipts exposes subsession.
Tests: 96 passing. The concurrent-guard test was verified to fail against
an unguarded check-then-append before being accepted.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Subsessions replaced first-write-wins, so the old wording is stale. The
limit is now that receipts are cumulative and never superseding: each
subsession carries the whole session up to that moment, no single receipt
is 'the' record, and counting receipts is not counting sessions. Work
after the final SessionEnd firing is still never captured.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for freeto 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.

1 participant

@githubscum
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Subsession receipts with a no-change guard - #2

Merged
githubscum merged 2 commits into
mainfrom
feat/subsessions
Jul 22, 2026
Merged

Subsession receipts with a no-change guard#2
githubscum merged 2 commits into
mainfrom
feat/subsessions

Conversation

@githubscum

Copy link
Copy Markdown
Owner

Closes KNOWN-LIMITS item 5. Follows the merged install-readiness and session-receipt work in #1.

The problem

Claude Code fires SessionEnd more than once for the same session (on clear, on resume, on exit). The hook deduped by session id, so the first firing won and everything after it was lost. A session that got receipted and then kept working left no record of the later work.

The fix

Each firing that carries new activity appends a NEW receipt, indexed subsession 0, 1, ... n.

Receipts stay cumulative rather than deltas. Every firing carries the whole transcript, so each receipt is a complete self-contained summary that stands on its own. Receipts are summaries (digests and counts, never file contents), so the redundancy is cheap and buys back the property that any single receipt is independently meaningful.

A no-change guard prevents inflation: if the transcript has not grown since the last receipt for that session, nothing is appended.

The race this had to avoid

The subsession decision reads the chain and then appends, which is a time-of-check-to-time-of-use race. Two firings for the same session can both observe "no receipt yet" and both append subsession 0.

appendReceiptGuarded(buildPayload) runs the whole check-then-append inside the existing chain lock. The callback receives the freshly re-read tail and returns either a payload or null to skip.

Reproduced before fixing: 4 barrier-synchronized processes ingesting the same transcript produced 4 receipts instead of 1. The test was verified to fail against an unguarded check-then-append before being accepted, because a concurrency test that passes both before and after a fix is worthless.

Changes

  • src/store/index.js: new appendReceiptGuarded. Existing appendReceipt, reload, verify untouched.
  • src/parser/index.js: counts.transcriptEntries is the growth marker.
  • src/ingest/index.js: owns the subsession decision so the hook and the manual CLI share one implementation. Returns {entry, skipped, subsession, sessionId}.
  • bin/hook-session-end.js: drops its own dedupe. Every existing guarantee kept: never writes stdout, never exits non-zero, catches all failures, bounded stdin read.
  • src/views/index.js: morning-after reports total session receipts AND distinct sessions, since one session can now produce several receipts and a single number misleads. Session receipts show a subsession line when present, omitted for older receipts.
  • src/mcp/server.js: query_receipts exposes subsession.
  • KNOWN-LIMITS.md: item 5 rewritten for the new behavior.

Verification

  • 96 tests passing (88 baseline plus 8).
  • Concurrent-guard test proven to fail against the unguarded implementation and pass against the guarded one.
  • Real-transcript check: first run appends subsession 0, second run reports no new activity and appends nothing.

githubscumand others added 2 commits July 22, 2026 11:36
Closes KNOWN-LIMITS 5 (first-write-wins session receipts). Claude Code
fires SessionEnd more than once per session (clear, resume, exit), so a
session that was receipted and then kept working lost that later work.
Each qualifying firing now appends a NEW receipt indexed subsession
0, 1, ... n. Receipts stay cumulative rather than deltas: every firing
carries the whole transcript, so each receipt is a complete self-contained
summary that stands alone. Receipts are summaries (digests and counts,
never file contents), so the redundancy is cheap.
A no-change guard prevents inflation: if the transcript has not grown
since the last receipt for that session, nothing is appended.
- store: new appendReceiptGuarded(buildPayload) runs the whole
check-then-append inside the chain lock. Without it the subsession
decision is a TOCTOU: two firings both observe "no receipt yet" and both
append subsession 0. Reproduced with 4 barrier-synchronized processes,
which yielded 4 receipts instead of 1.
- parser: counts.transcriptEntries is the growth marker.
- ingest: owns the subsession decision so the hook and the manual CLI
share it. Returns {entry, skipped, subsession, sessionId}.
- hook: drops its own dedupe, reports the appended subsession or the skip.
Every existing guarantee kept (never stdout, never non-zero, catch all).
- views: morning-after reports total session receipts AND distinct
sessions, since one session can now produce several receipts and a
single number misleads. Session receipt shows the subsession line when
present, omitted for older receipts.
- MCP: query_receipts exposes subsession.
Tests: 96 passing. The concurrent-guard test was verified to fail against
an unguarded check-then-append before being accepted.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Subsessions replaced first-write-wins, so the old wording is stale. The
limit is now that receipts are cumulative and never superseding: each
subsession carries the whole session up to that moment, no single receipt
is 'the' record, and counting receipts is not counting sessions. Work
after the final SessionEnd firing is still never captured.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for freeto 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.

1 participant

@githubscum
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Subsession receipts with a no-change guard - #2

Merged
githubscum merged 2 commits into
mainfrom
feat/subsessions
Jul 22, 2026
Merged

Subsession receipts with a no-change guard#2
githubscum merged 2 commits into
mainfrom
feat/subsessions

Conversation

@githubscum

Copy link
Copy Markdown
Owner

Closes KNOWN-LIMITS item 5. Follows the merged install-readiness and session-receipt work in #1.

The problem

Claude Code fires SessionEnd more than once for the same session (on clear, on resume, on exit). The hook deduped by session id, so the first firing won and everything after it was lost. A session that got receipted and then kept working left no record of the later work.

The fix

Each firing that carries new activity appends a NEW receipt, indexed subsession 0, 1, ... n.

Receipts stay cumulative rather than deltas. Every firing carries the whole transcript, so each receipt is a complete self-contained summary that stands on its own. Receipts are summaries (digests and counts, never file contents), so the redundancy is cheap and buys back the property that any single receipt is independently meaningful.

A no-change guard prevents inflation: if the transcript has not grown since the last receipt for that session, nothing is appended.

The race this had to avoid

The subsession decision reads the chain and then appends, which is a time-of-check-to-time-of-use race. Two firings for the same session can both observe "no receipt yet" and both append subsession 0.

appendReceiptGuarded(buildPayload) runs the whole check-then-append inside the existing chain lock. The callback receives the freshly re-read tail and returns either a payload or null to skip.

Reproduced before fixing: 4 barrier-synchronized processes ingesting the same transcript produced 4 receipts instead of 1. The test was verified to fail against an unguarded check-then-append before being accepted, because a concurrency test that passes both before and after a fix is worthless.

Changes

  • src/store/index.js: new appendReceiptGuarded. Existing appendReceipt, reload, verify untouched.
  • src/parser/index.js: counts.transcriptEntries is the growth marker.
  • src/ingest/index.js: owns the subsession decision so the hook and the manual CLI share one implementation. Returns {entry, skipped, subsession, sessionId}.
  • bin/hook-session-end.js: drops its own dedupe. Every existing guarantee kept: never writes stdout, never exits non-zero, catches all failures, bounded stdin read.
  • src/views/index.js: morning-after reports total session receipts AND distinct sessions, since one session can now produce several receipts and a single number misleads. Session receipts show a subsession line when present, omitted for older receipts.
  • src/mcp/server.js: query_receipts exposes subsession.
  • KNOWN-LIMITS.md: item 5 rewritten for the new behavior.

Verification

  • 96 tests passing (88 baseline plus 8).
  • Concurrent-guard test proven to fail against the unguarded implementation and pass against the guarded one.
  • Real-transcript check: first run appends subsession 0, second run reports no new activity and appends nothing.

githubscumand others added 2 commits July 22, 2026 11:36
Closes KNOWN-LIMITS 5 (first-write-wins session receipts). Claude Code
fires SessionEnd more than once per session (clear, resume, exit), so a
session that was receipted and then kept working lost that later work.
Each qualifying firing now appends a NEW receipt indexed subsession
0, 1, ... n. Receipts stay cumulative rather than deltas: every firing
carries the whole transcript, so each receipt is a complete self-contained
summary that stands alone. Receipts are summaries (digests and counts,
never file contents), so the redundancy is cheap.
A no-change guard prevents inflation: if the transcript has not grown
since the last receipt for that session, nothing is appended.
- store: new appendReceiptGuarded(buildPayload) runs the whole
check-then-append inside the chain lock. Without it the subsession
decision is a TOCTOU: two firings both observe "no receipt yet" and both
append subsession 0. Reproduced with 4 barrier-synchronized processes,
which yielded 4 receipts instead of 1.
- parser: counts.transcriptEntries is the growth marker.
- ingest: owns the subsession decision so the hook and the manual CLI
share it. Returns {entry, skipped, subsession, sessionId}.
- hook: drops its own dedupe, reports the appended subsession or the skip.
Every existing guarantee kept (never stdout, never non-zero, catch all).
- views: morning-after reports total session receipts AND distinct
sessions, since one session can now produce several receipts and a
single number misleads. Session receipt shows the subsession line when
present, omitted for older receipts.
- MCP: query_receipts exposes subsession.
Tests: 96 passing. The concurrent-guard test was verified to fail against
an unguarded check-then-append before being accepted.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Subsessions replaced first-write-wins, so the old wording is stale. The
limit is now that receipts are cumulative and never superseding: each
subsession carries the whole session up to that moment, no single receipt
is 'the' record, and counting receipts is not counting sessions. Work
after the final SessionEnd firing is still never captured.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for freeto 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.

1 participant

@githubscum
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Subsession receipts with a no-change guard - #2

Merged
githubscum merged 2 commits into
mainfrom
feat/subsessions
Jul 22, 2026
Merged

Subsession receipts with a no-change guard#2
githubscum merged 2 commits into
mainfrom
feat/subsessions

Conversation

@githubscum

Copy link
Copy Markdown
Owner

Closes KNOWN-LIMITS item 5. Follows the merged install-readiness and session-receipt work in #1.

The problem

Claude Code fires SessionEnd more than once for the same session (on clear, on resume, on exit). The hook deduped by session id, so the first firing won and everything after it was lost. A session that got receipted and then kept working left no record of the later work.

The fix

Each firing that carries new activity appends a NEW receipt, indexed subsession 0, 1, ... n.

Receipts stay cumulative rather than deltas. Every firing carries the whole transcript, so each receipt is a complete self-contained summary that stands on its own. Receipts are summaries (digests and counts, never file contents), so the redundancy is cheap and buys back the property that any single receipt is independently meaningful.

A no-change guard prevents inflation: if the transcript has not grown since the last receipt for that session, nothing is appended.

The race this had to avoid

The subsession decision reads the chain and then appends, which is a time-of-check-to-time-of-use race. Two firings for the same session can both observe "no receipt yet" and both append subsession 0.

appendReceiptGuarded(buildPayload) runs the whole check-then-append inside the existing chain lock. The callback receives the freshly re-read tail and returns either a payload or null to skip.

Reproduced before fixing: 4 barrier-synchronized processes ingesting the same transcript produced 4 receipts instead of 1. The test was verified to fail against an unguarded check-then-append before being accepted, because a concurrency test that passes both before and after a fix is worthless.

Changes

  • src/store/index.js: new appendReceiptGuarded. Existing appendReceipt, reload, verify untouched.
  • src/parser/index.js: counts.transcriptEntries is the growth marker.
  • src/ingest/index.js: owns the subsession decision so the hook and the manual CLI share one implementation. Returns {entry, skipped, subsession, sessionId}.
  • bin/hook-session-end.js: drops its own dedupe. Every existing guarantee kept: never writes stdout, never exits non-zero, catches all failures, bounded stdin read.
  • src/views/index.js: morning-after reports total session receipts AND distinct sessions, since one session can now produce several receipts and a single number misleads. Session receipts show a subsession line when present, omitted for older receipts.
  • src/mcp/server.js: query_receipts exposes subsession.
  • KNOWN-LIMITS.md: item 5 rewritten for the new behavior.

Verification

  • 96 tests passing (88 baseline plus 8).
  • Concurrent-guard test proven to fail against the unguarded implementation and pass against the guarded one.
  • Real-transcript check: first run appends subsession 0, second run reports no new activity and appends nothing.

githubscumand others added 2 commits July 22, 2026 11:36
Closes KNOWN-LIMITS 5 (first-write-wins session receipts). Claude Code
fires SessionEnd more than once per session (clear, resume, exit), so a
session that was receipted and then kept working lost that later work.
Each qualifying firing now appends a NEW receipt indexed subsession
0, 1, ... n. Receipts stay cumulative rather than deltas: every firing
carries the whole transcript, so each receipt is a complete self-contained
summary that stands alone. Receipts are summaries (digests and counts,
never file contents), so the redundancy is cheap.
A no-change guard prevents inflation: if the transcript has not grown
since the last receipt for that session, nothing is appended.
- store: new appendReceiptGuarded(buildPayload) runs the whole
check-then-append inside the chain lock. Without it the subsession
decision is a TOCTOU: two firings both observe "no receipt yet" and both
append subsession 0. Reproduced with 4 barrier-synchronized processes,
which yielded 4 receipts instead of 1.
- parser: counts.transcriptEntries is the growth marker.
- ingest: owns the subsession decision so the hook and the manual CLI
share it. Returns {entry, skipped, subsession, sessionId}.
- hook: drops its own dedupe, reports the appended subsession or the skip.
Every existing guarantee kept (never stdout, never non-zero, catch all).
- views: morning-after reports total session receipts AND distinct
sessions, since one session can now produce several receipts and a
single number misleads. Session receipt shows the subsession line when
present, omitted for older receipts.
- MCP: query_receipts exposes subsession.
Tests: 96 passing. The concurrent-guard test was verified to fail against
an unguarded check-then-append before being accepted.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Subsessions replaced first-write-wins, so the old wording is stale. The
limit is now that receipts are cumulative and never superseding: each
subsession carries the whole session up to that moment, no single receipt
is 'the' record, and counting receipts is not counting sessions. Work
after the final SessionEnd firing is still never captured.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for freeto 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.

1 participant

@githubscum
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Subsession receipts with a no-change guard - #2

Merged
githubscum merged 2 commits into
mainfrom
feat/subsessions
Jul 22, 2026
Merged

Subsession receipts with a no-change guard#2
githubscum merged 2 commits into
mainfrom
feat/subsessions

Conversation

@githubscum

Copy link
Copy Markdown
Owner

Closes KNOWN-LIMITS item 5. Follows the merged install-readiness and session-receipt work in #1.

The problem

Claude Code fires SessionEnd more than once for the same session (on clear, on resume, on exit). The hook deduped by session id, so the first firing won and everything after it was lost. A session that got receipted and then kept working left no record of the later work.

The fix

Each firing that carries new activity appends a NEW receipt, indexed subsession 0, 1, ... n.

Receipts stay cumulative rather than deltas. Every firing carries the whole transcript, so each receipt is a complete self-contained summary that stands on its own. Receipts are summaries (digests and counts, never file contents), so the redundancy is cheap and buys back the property that any single receipt is independently meaningful.

A no-change guard prevents inflation: if the transcript has not grown since the last receipt for that session, nothing is appended.

The race this had to avoid

The subsession decision reads the chain and then appends, which is a time-of-check-to-time-of-use race. Two firings for the same session can both observe "no receipt yet" and both append subsession 0.

appendReceiptGuarded(buildPayload) runs the whole check-then-append inside the existing chain lock. The callback receives the freshly re-read tail and returns either a payload or null to skip.

Reproduced before fixing: 4 barrier-synchronized processes ingesting the same transcript produced 4 receipts instead of 1. The test was verified to fail against an unguarded check-then-append before being accepted, because a concurrency test that passes both before and after a fix is worthless.

Changes

  • src/store/index.js: new appendReceiptGuarded. Existing appendReceipt, reload, verify untouched.
  • src/parser/index.js: counts.transcriptEntries is the growth marker.
  • src/ingest/index.js: owns the subsession decision so the hook and the manual CLI share one implementation. Returns {entry, skipped, subsession, sessionId}.
  • bin/hook-session-end.js: drops its own dedupe. Every existing guarantee kept: never writes stdout, never exits non-zero, catches all failures, bounded stdin read.
  • src/views/index.js: morning-after reports total session receipts AND distinct sessions, since one session can now produce several receipts and a single number misleads. Session receipts show a subsession line when present, omitted for older receipts.
  • src/mcp/server.js: query_receipts exposes subsession.
  • KNOWN-LIMITS.md: item 5 rewritten for the new behavior.

Verification

  • 96 tests passing (88 baseline plus 8).
  • Concurrent-guard test proven to fail against the unguarded implementation and pass against the guarded one.
  • Real-transcript check: first run appends subsession 0, second run reports no new activity and appends nothing.

githubscumand others added 2 commits July 22, 2026 11:36
Closes KNOWN-LIMITS 5 (first-write-wins session receipts). Claude Code
fires SessionEnd more than once per session (clear, resume, exit), so a
session that was receipted and then kept working lost that later work.
Each qualifying firing now appends a NEW receipt indexed subsession
0, 1, ... n. Receipts stay cumulative rather than deltas: every firing
carries the whole transcript, so each receipt is a complete self-contained
summary that stands alone. Receipts are summaries (digests and counts,
never file contents), so the redundancy is cheap.
A no-change guard prevents inflation: if the transcript has not grown
since the last receipt for that session, nothing is appended.
- store: new appendReceiptGuarded(buildPayload) runs the whole
check-then-append inside the chain lock. Without it the subsession
decision is a TOCTOU: two firings both observe "no receipt yet" and both
append subsession 0. Reproduced with 4 barrier-synchronized processes,
which yielded 4 receipts instead of 1.
- parser: counts.transcriptEntries is the growth marker.
- ingest: owns the subsession decision so the hook and the manual CLI
share it. Returns {entry, skipped, subsession, sessionId}.
- hook: drops its own dedupe, reports the appended subsession or the skip.
Every existing guarantee kept (never stdout, never non-zero, catch all).
- views: morning-after reports total session receipts AND distinct
sessions, since one session can now produce several receipts and a
single number misleads. Session receipt shows the subsession line when
present, omitted for older receipts.
- MCP: query_receipts exposes subsession.
Tests: 96 passing. The concurrent-guard test was verified to fail against
an unguarded check-then-append before being accepted.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Subsessions replaced first-write-wins, so the old wording is stale. The
limit is now that receipts are cumulative and never superseding: each
subsession carries the whole session up to that moment, no single receipt
is 'the' record, and counting receipts is not counting sessions. Work
after the final SessionEnd firing is still never captured.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for freeto 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.

1 participant

@githubscum
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Subsession receipts with a no-change guard - #2

Merged
githubscum merged 2 commits into
mainfrom
feat/subsessions
Jul 22, 2026
Merged

Subsession receipts with a no-change guard#2
githubscum merged 2 commits into
mainfrom
feat/subsessions

Conversation

@githubscum

Copy link
Copy Markdown
Owner

Closes KNOWN-LIMITS item 5. Follows the merged install-readiness and session-receipt work in #1.

The problem

Claude Code fires SessionEnd more than once for the same session (on clear, on resume, on exit). The hook deduped by session id, so the first firing won and everything after it was lost. A session that got receipted and then kept working left no record of the later work.

The fix

Each firing that carries new activity appends a NEW receipt, indexed subsession 0, 1, ... n.

Receipts stay cumulative rather than deltas. Every firing carries the whole transcript, so each receipt is a complete self-contained summary that stands on its own. Receipts are summaries (digests and counts, never file contents), so the redundancy is cheap and buys back the property that any single receipt is independently meaningful.

A no-change guard prevents inflation: if the transcript has not grown since the last receipt for that session, nothing is appended.

The race this had to avoid

The subsession decision reads the chain and then appends, which is a time-of-check-to-time-of-use race. Two firings for the same session can both observe "no receipt yet" and both append subsession 0.

appendReceiptGuarded(buildPayload) runs the whole check-then-append inside the existing chain lock. The callback receives the freshly re-read tail and returns either a payload or null to skip.

Reproduced before fixing: 4 barrier-synchronized processes ingesting the same transcript produced 4 receipts instead of 1. The test was verified to fail against an unguarded check-then-append before being accepted, because a concurrency test that passes both before and after a fix is worthless.

Changes

  • src/store/index.js: new appendReceiptGuarded. Existing appendReceipt, reload, verify untouched.
  • src/parser/index.js: counts.transcriptEntries is the growth marker.
  • src/ingest/index.js: owns the subsession decision so the hook and the manual CLI share one implementation. Returns {entry, skipped, subsession, sessionId}.
  • bin/hook-session-end.js: drops its own dedupe. Every existing guarantee kept: never writes stdout, never exits non-zero, catches all failures, bounded stdin read.
  • src/views/index.js: morning-after reports total session receipts AND distinct sessions, since one session can now produce several receipts and a single number misleads. Session receipts show a subsession line when present, omitted for older receipts.
  • src/mcp/server.js: query_receipts exposes subsession.
  • KNOWN-LIMITS.md: item 5 rewritten for the new behavior.

Verification

  • 96 tests passing (88 baseline plus 8).
  • Concurrent-guard test proven to fail against the unguarded implementation and pass against the guarded one.
  • Real-transcript check: first run appends subsession 0, second run reports no new activity and appends nothing.

githubscumand others added 2 commits July 22, 2026 11:36
Closes KNOWN-LIMITS 5 (first-write-wins session receipts). Claude Code
fires SessionEnd more than once per session (clear, resume, exit), so a
session that was receipted and then kept working lost that later work.
Each qualifying firing now appends a NEW receipt indexed subsession
0, 1, ... n. Receipts stay cumulative rather than deltas: every firing
carries the whole transcript, so each receipt is a complete self-contained
summary that stands alone. Receipts are summaries (digests and counts,
never file contents), so the redundancy is cheap.
A no-change guard prevents inflation: if the transcript has not grown
since the last receipt for that session, nothing is appended.
- store: new appendReceiptGuarded(buildPayload) runs the whole
check-then-append inside the chain lock. Without it the subsession
decision is a TOCTOU: two firings both observe "no receipt yet" and both
append subsession 0. Reproduced with 4 barrier-synchronized processes,
which yielded 4 receipts instead of 1.
- parser: counts.transcriptEntries is the growth marker.
- ingest: owns the subsession decision so the hook and the manual CLI
share it. Returns {entry, skipped, subsession, sessionId}.
- hook: drops its own dedupe, reports the appended subsession or the skip.
Every existing guarantee kept (never stdout, never non-zero, catch all).
- views: morning-after reports total session receipts AND distinct
sessions, since one session can now produce several receipts and a
single number misleads. Session receipt shows the subsession line when
present, omitted for older receipts.
- MCP: query_receipts exposes subsession.
Tests: 96 passing. The concurrent-guard test was verified to fail against
an unguarded check-then-append before being accepted.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Subsessions replaced first-write-wins, so the old wording is stale. The
limit is now that receipts are cumulative and never superseding: each
subsession carries the whole session up to that moment, no single receipt
is 'the' record, and counting receipts is not counting sessions. Work
after the final SessionEnd firing is still never captured.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for freeto 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.

1 participant

@githubscum
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Subsession receipts with a no-change guard - #2

Merged
githubscum merged 2 commits into
mainfrom
feat/subsessions
Jul 22, 2026
Merged

Subsession receipts with a no-change guard#2
githubscum merged 2 commits into
mainfrom
feat/subsessions

Conversation

@githubscum

Copy link
Copy Markdown
Owner

Closes KNOWN-LIMITS item 5. Follows the merged install-readiness and session-receipt work in #1.

The problem

Claude Code fires SessionEnd more than once for the same session (on clear, on resume, on exit). The hook deduped by session id, so the first firing won and everything after it was lost. A session that got receipted and then kept working left no record of the later work.

The fix

Each firing that carries new activity appends a NEW receipt, indexed subsession 0, 1, ... n.

Receipts stay cumulative rather than deltas. Every firing carries the whole transcript, so each receipt is a complete self-contained summary that stands on its own. Receipts are summaries (digests and counts, never file contents), so the redundancy is cheap and buys back the property that any single receipt is independently meaningful.

A no-change guard prevents inflation: if the transcript has not grown since the last receipt for that session, nothing is appended.

The race this had to avoid

The subsession decision reads the chain and then appends, which is a time-of-check-to-time-of-use race. Two firings for the same session can both observe "no receipt yet" and both append subsession 0.

appendReceiptGuarded(buildPayload) runs the whole check-then-append inside the existing chain lock. The callback receives the freshly re-read tail and returns either a payload or null to skip.

Reproduced before fixing: 4 barrier-synchronized processes ingesting the same transcript produced 4 receipts instead of 1. The test was verified to fail against an unguarded check-then-append before being accepted, because a concurrency test that passes both before and after a fix is worthless.

Changes

  • src/store/index.js: new appendReceiptGuarded. Existing appendReceipt, reload, verify untouched.
  • src/parser/index.js: counts.transcriptEntries is the growth marker.
  • src/ingest/index.js: owns the subsession decision so the hook and the manual CLI share one implementation. Returns {entry, skipped, subsession, sessionId}.
  • bin/hook-session-end.js: drops its own dedupe. Every existing guarantee kept: never writes stdout, never exits non-zero, catches all failures, bounded stdin read.
  • src/views/index.js: morning-after reports total session receipts AND distinct sessions, since one session can now produce several receipts and a single number misleads. Session receipts show a subsession line when present, omitted for older receipts.
  • src/mcp/server.js: query_receipts exposes subsession.
  • KNOWN-LIMITS.md: item 5 rewritten for the new behavior.

Verification

  • 96 tests passing (88 baseline plus 8).
  • Concurrent-guard test proven to fail against the unguarded implementation and pass against the guarded one.
  • Real-transcript check: first run appends subsession 0, second run reports no new activity and appends nothing.

githubscumand others added 2 commits July 22, 2026 11:36
Closes KNOWN-LIMITS 5 (first-write-wins session receipts). Claude Code
fires SessionEnd more than once per session (clear, resume, exit), so a
session that was receipted and then kept working lost that later work.
Each qualifying firing now appends a NEW receipt indexed subsession
0, 1, ... n. Receipts stay cumulative rather than deltas: every firing
carries the whole transcript, so each receipt is a complete self-contained
summary that stands alone. Receipts are summaries (digests and counts,
never file contents), so the redundancy is cheap.
A no-change guard prevents inflation: if the transcript has not grown
since the last receipt for that session, nothing is appended.
- store: new appendReceiptGuarded(buildPayload) runs the whole
check-then-append inside the chain lock. Without it the subsession
decision is a TOCTOU: two firings both observe "no receipt yet" and both
append subsession 0. Reproduced with 4 barrier-synchronized processes,
which yielded 4 receipts instead of 1.
- parser: counts.transcriptEntries is the growth marker.
- ingest: owns the subsession decision so the hook and the manual CLI
share it. Returns {entry, skipped, subsession, sessionId}.
- hook: drops its own dedupe, reports the appended subsession or the skip.
Every existing guarantee kept (never stdout, never non-zero, catch all).
- views: morning-after reports total session receipts AND distinct
sessions, since one session can now produce several receipts and a
single number misleads. Session receipt shows the subsession line when
present, omitted for older receipts.
- MCP: query_receipts exposes subsession.
Tests: 96 passing. The concurrent-guard test was verified to fail against
an unguarded check-then-append before being accepted.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Subsessions replaced first-write-wins, so the old wording is stale. The
limit is now that receipts are cumulative and never superseding: each
subsession carries the whole session up to that moment, no single receipt
is 'the' record, and counting receipts is not counting sessions. Work
after the final SessionEnd firing is still never captured.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for freeto 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.

1 participant

@githubscum
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Subsession receipts with a no-change guard - #2

Merged
githubscum merged 2 commits into
mainfrom
feat/subsessions
Jul 22, 2026
Merged

Subsession receipts with a no-change guard#2
githubscum merged 2 commits into
mainfrom
feat/subsessions

Conversation

@githubscum

Copy link
Copy Markdown
Owner

Closes KNOWN-LIMITS item 5. Follows the merged install-readiness and session-receipt work in #1.

The problem

Claude Code fires SessionEnd more than once for the same session (on clear, on resume, on exit). The hook deduped by session id, so the first firing won and everything after it was lost. A session that got receipted and then kept working left no record of the later work.

The fix

Each firing that carries new activity appends a NEW receipt, indexed subsession 0, 1, ... n.

Receipts stay cumulative rather than deltas. Every firing carries the whole transcript, so each receipt is a complete self-contained summary that stands on its own. Receipts are summaries (digests and counts, never file contents), so the redundancy is cheap and buys back the property that any single receipt is independently meaningful.

A no-change guard prevents inflation: if the transcript has not grown since the last receipt for that session, nothing is appended.

The race this had to avoid

The subsession decision reads the chain and then appends, which is a time-of-check-to-time-of-use race. Two firings for the same session can both observe "no receipt yet" and both append subsession 0.

appendReceiptGuarded(buildPayload) runs the whole check-then-append inside the existing chain lock. The callback receives the freshly re-read tail and returns either a payload or null to skip.

Reproduced before fixing: 4 barrier-synchronized processes ingesting the same transcript produced 4 receipts instead of 1. The test was verified to fail against an unguarded check-then-append before being accepted, because a concurrency test that passes both before and after a fix is worthless.

Changes

  • src/store/index.js: new appendReceiptGuarded. Existing appendReceipt, reload, verify untouched.
  • src/parser/index.js: counts.transcriptEntries is the growth marker.
  • src/ingest/index.js: owns the subsession decision so the hook and the manual CLI share one implementation. Returns {entry, skipped, subsession, sessionId}.
  • bin/hook-session-end.js: drops its own dedupe. Every existing guarantee kept: never writes stdout, never exits non-zero, catches all failures, bounded stdin read.
  • src/views/index.js: morning-after reports total session receipts AND distinct sessions, since one session can now produce several receipts and a single number misleads. Session receipts show a subsession line when present, omitted for older receipts.
  • src/mcp/server.js: query_receipts exposes subsession.
  • KNOWN-LIMITS.md: item 5 rewritten for the new behavior.

Verification

  • 96 tests passing (88 baseline plus 8).
  • Concurrent-guard test proven to fail against the unguarded implementation and pass against the guarded one.
  • Real-transcript check: first run appends subsession 0, second run reports no new activity and appends nothing.

githubscumand others added 2 commits July 22, 2026 11:36
Closes KNOWN-LIMITS 5 (first-write-wins session receipts). Claude Code
fires SessionEnd more than once per session (clear, resume, exit), so a
session that was receipted and then kept working lost that later work.
Each qualifying firing now appends a NEW receipt indexed subsession
0, 1, ... n. Receipts stay cumulative rather than deltas: every firing
carries the whole transcript, so each receipt is a complete self-contained
summary that stands alone. Receipts are summaries (digests and counts,
never file contents), so the redundancy is cheap.
A no-change guard prevents inflation: if the transcript has not grown
since the last receipt for that session, nothing is appended.
- store: new appendReceiptGuarded(buildPayload) runs the whole
check-then-append inside the chain lock. Without it the subsession
decision is a TOCTOU: two firings both observe "no receipt yet" and both
append subsession 0. Reproduced with 4 barrier-synchronized processes,
which yielded 4 receipts instead of 1.
- parser: counts.transcriptEntries is the growth marker.
- ingest: owns the subsession decision so the hook and the manual CLI
share it. Returns {entry, skipped, subsession, sessionId}.
- hook: drops its own dedupe, reports the appended subsession or the skip.
Every existing guarantee kept (never stdout, never non-zero, catch all).
- views: morning-after reports total session receipts AND distinct
sessions, since one session can now produce several receipts and a
single number misleads. Session receipt shows the subsession line when
present, omitted for older receipts.
- MCP: query_receipts exposes subsession.
Tests: 96 passing. The concurrent-guard test was verified to fail against
an unguarded check-then-append before being accepted.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Subsessions replaced first-write-wins, so the old wording is stale. The
limit is now that receipts are cumulative and never superseding: each
subsession carries the whole session up to that moment, no single receipt
is 'the' record, and counting receipts is not counting sessions. Work
after the final SessionEnd firing is still never captured.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for freeto 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.

1 participant

@githubscum