Following up on #972, which was closed 2026-07-07 with the note that "the hook now reads the work.json registry, so the WORK→LEARNING bridge actually runs".
The two reported bugs are indeed fixed in the current code. The hook still never writes a file, for two different reasons — and I think the second one raises a design question worth asking before anyone fixes the first.
Tested on 7.1.1. hooks/WorkCompletionLearning.hook.ts and hooks/lib/isa-utils.ts are byte-identical to the files in this repo at HEAD.
It has never produced a file
The hook's output filename is ${date}_${time}_work_${slug}.md (writeLearning()). I searched a MEMORY/LEARNING/ corpus of 1077 files accumulated over five months of daily use on 4.0.3, plus the current 7.1.1 tree:
Zero files matching *_work_*.md in either. Every file in that corpus came from the rating/sentiment capture hook, which uses a different name pattern. The folder looks healthy, which is likely why this has stayed invisible across #648, #668 and #972.
Blocker 1 — the ending session is never found
main() resolves the session via findActiveSessionByUUID(sessionId). That helper skips any row whose phase is not in ACTIVE_LOOKUP_PHASES (hooks/lib/isa-utils.ts:541), which deliberately excludes native.
Every session is registered as a native-<uuid> placeholder row at start. The row is only re-bound to a real ISA slug when ISASync.hook.ts fires — a PostToolUse hook on Write/Edit. So:
- A session that works on an ISA but appends to it via a shell redirect never re-binds.
- An ISA stays bound to the UUID of the session that first synced it, so a later session working the same ISA is invisible at its own SessionEnd.
Observed: EventLogger kept attributing tool activity to the correct ISA slug for hours while the row still carried a two-day-old session UUID.
$ echo '{"session_id":"<current-session>"}' | bun hooks/WorkCompletionLearning.hook.ts
[WorkCompletionLearning] No active work session for sessionId=<current-session>
Blocker 2 — a resolved row is still classified trivial
With a row that does resolve, the significance gate is:
const filesChangedCount = workMeta.lineage?.files_changed?.length || 0;
const hasSignificantWork = filesChangedCount > 0 || workMeta.source === 'MANUAL';
lineage and source are META.yaml-era fields. ISA frontmatter carries slug, phase, progress, iteration, frozen, created, resumed_at — neither field exists, so filesChangedCount is always 0 and source is always undefined.
$ echo '{"session_id":"<uuid-of-a-real-ISA-row>"}' | bun hooks/WorkCompletionLearning.hook.ts
[WorkCompletionLearning] Trivial work session, skipping learning capture
That row was a multi-day project with 26 criteria and dozens of changed files.
The two blockers are independent — fixing either alone changes nothing.
The design question
This is asked genuinely, not rhetorically: what is meant to fill the Insights section?
With both blockers fixed, a produced file would read:
## What Was Done
- **Files Changed:** 0
- **Tools Used:** None tracked
- **Agents Spawned:** 0
## Insights
*This work session completed successfully. Consider what made it effective:*
- Was the approach straightforward or did it require iteration?
- Were there any blockers or surprises?
- What patterns from this work apply to future tasks?
The three counters are the fields from Blocker 2, so they would stay at zero unless something starts populating them. And the section named Insights contains three fixed questions, identical in every file that would ever be written.
If a later pass is meant to answer them, I could not find what triggers it. If the answers are meant to be model-written, a SessionEnd shell hook may be the wrong place for it, since it has no model to ask — which would make the empty middle a structural property rather than an omission.
I ask because the fix effort depends entirely on the answer:
- If the intent is a work log, then the metadata header is the product, and the
Insights block is arguably better removed than filled. Also worth noting it overlaps with what the session-summary path already writes.
- If the intent is a learning, the hook needs a producer for the insight, and the two blockers are the smaller half of the problem.
Happy to open a PR for either shape once the intent is clear. Fixing the two blockers alone is straightforward, but it would start emitting files whose most prominent section is empty by construction, so it seemed worth asking first.
Environment
- LifeOS 7.1.1, Claude Code, macOS
hooks/WorkCompletionLearning.hook.ts v1.5.12, unmodified
hooks/lib/isa-utils.ts, unmodified
Following up on #972, which was closed 2026-07-07 with the note that "the hook now reads the work.json registry, so the WORK→LEARNING bridge actually runs".
The two reported bugs are indeed fixed in the current code. The hook still never writes a file, for two different reasons — and I think the second one raises a design question worth asking before anyone fixes the first.
Tested on 7.1.1.
hooks/WorkCompletionLearning.hook.tsandhooks/lib/isa-utils.tsare byte-identical to the files in this repo at HEAD.It has never produced a file
The hook's output filename is
${date}_${time}_work_${slug}.md(writeLearning()). I searched aMEMORY/LEARNING/corpus of 1077 files accumulated over five months of daily use on 4.0.3, plus the current 7.1.1 tree:Zero files matching
*_work_*.mdin either. Every file in that corpus came from the rating/sentiment capture hook, which uses a different name pattern. The folder looks healthy, which is likely why this has stayed invisible across #648, #668 and #972.Blocker 1 — the ending session is never found
main()resolves the session viafindActiveSessionByUUID(sessionId). That helper skips any row whose phase is not inACTIVE_LOOKUP_PHASES(hooks/lib/isa-utils.ts:541), which deliberately excludesnative.Every session is registered as a
native-<uuid>placeholder row at start. The row is only re-bound to a real ISA slug whenISASync.hook.tsfires — a PostToolUse hook on Write/Edit. So:Observed:
EventLoggerkept attributing tool activity to the correct ISA slug for hours while the row still carried a two-day-old session UUID.Blocker 2 — a resolved row is still classified trivial
With a row that does resolve, the significance gate is:
lineageandsourceare META.yaml-era fields. ISA frontmatter carriesslug,phase,progress,iteration,frozen,created,resumed_at— neither field exists, sofilesChangedCountis always 0 andsourceis always undefined.That row was a multi-day project with 26 criteria and dozens of changed files.
The two blockers are independent — fixing either alone changes nothing.
The design question
This is asked genuinely, not rhetorically: what is meant to fill the
Insightssection?With both blockers fixed, a produced file would read:
The three counters are the fields from Blocker 2, so they would stay at zero unless something starts populating them. And the section named
Insightscontains three fixed questions, identical in every file that would ever be written.If a later pass is meant to answer them, I could not find what triggers it. If the answers are meant to be model-written, a
SessionEndshell hook may be the wrong place for it, since it has no model to ask — which would make the empty middle a structural property rather than an omission.I ask because the fix effort depends entirely on the answer:
Insightsblock is arguably better removed than filled. Also worth noting it overlaps with what the session-summary path already writes.Happy to open a PR for either shape once the intent is clear. Fixing the two blockers alone is straightforward, but it would start emitting files whose most prominent section is empty by construction, so it seemed worth asking first.
Environment
hooks/WorkCompletionLearning.hook.tsv1.5.12, unmodifiedhooks/lib/isa-utils.ts, unmodified