Skip to content

Stop-hook capture overwrites the per-session transcript each turn — only the first turn of every session ever syncs; later turns are silently dropped as "unchanged" #5

Description

@JUNX7764

Environment

Symptom

Every ZCode session syncs to Nowledge Mem with only its first turn (1 user + 1 assistant message). All subsequent turns in the same session are captured by the Stop hook but never imported. There is no error anywhere; the failure is silent.

Observed across 9 sessions spanning two days: every per-session transcript in plugins/data/nowledge-mem-zcode@nowledge-zcode/transcripts/ contains exactly 2 lines (the latest turn at capture time), and every zcode-sess_* thread in Nowledge Mem contains exactly the first turn.

Root cause

Three parts compound:

  1. The hook overwrites the per-session transcript instead of accumulating. copiedTranscriptPath() in hooks/zcode-mem-hook.mjs does writeFileSync(out, body) on a fixed per-session path. Because completeTranscript() pairs only the uncleared pending prompts (cleared after each successful sync) with the assistant-only Stop transcript, each Stop emits exactly one turn and replaces whatever was there before.

  2. nmem t sync reconciles an existing thread positionally, not by content. Re-syncing a transcript that contains only turn 2 for a thread that already has turn 1 returns:

    {"action": "unchanged", "messages_added": 0, "total_messages": 2}

    The 2 new messages are never appended — same count, same positions → "unchanged".

  3. --checkpointed can't cover the gap yet: nmem t sync --from zcode --checkpointed returns "checkpointed automatic capture is not yet available for zcode; use the normal sync path".

Proof the server appends fine when the transcript is cumulative

Controlled experiment with a throwaway session (cleaned up afterwards):

  • Sync a 2-message transcript → created.
  • Append 2 more messages to the same transcript file, sync again → "action": "appended", "messages_added": 2, "total_messages": 4. ✅

So the server-side reconciliation appends correctly as long as each sync receives the full conversation so far. The plugin is the side sending only the delta.

Suggested fixes

Any one of these closes the gap:

  1. Plugin: accumulate the per-session transcript. In copiedTranscriptPath(), append each reconstructed turn to the existing per-session file (or rebuild the full conversation from the sqlite store, as in the workaround described in Hooks silently not loaded from a local directory marketplace; ZCode 0.16.5 Stop transcript is assistant-only so stock sync always fails #2) instead of writeFileSync-overwriting it. This matches what t sync already expects and makes every sync idempotent.
  2. Server: enable --checkpointed capture for the zcode source, so per-turn deltas append from a durable checkpoint.
  3. In the meantime, a doctor/status check that compares the transcript's turn count against the thread's message count would surface the silent drop.

Footnote

The 0.2.1 changelog says new sessions "retain ordered user and assistant turns" — with this bug, that only holds for the first turn of each session. The save-handoff skill remains the only reliable path for multi-turn sessions today.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions