Devlog
Overview
Devlog is continuous, point-in-time work commentary: what happened, why, and anything surprising, captured as it happens instead of reconstructed from memory at the end of a session. One narrative session note accumulates every entry from the current session — no separate "learnings" system, no til/cursed split, everything worth remembering goes in the same place.
Processing (routing, connecting, daily-linking) happens later via
/second-brain:process-inbox. This skill only ever appends to the inbox.
Write the entry now, in the same turn as whatever triggered it. Don't
ask "should I log this?" and don't offer to — just write it. Asking is
exactly the failure mode that made the old distill-conversation flow get
skipped: by the time a session ends, you're mentally done, and a skip-able
step gets skipped.
When to write
- Finished a feature or fixed a notable bug
- Wrapping up a work session
- Discovered something non-obvious: a gotcha, undocumented behavior, a workaround that took several tries, a constraint you only noticed after hitting it
When not to write
- Routing, connecting, or moving existing notes — that's
/second-brain:process-inbox - Reading an external source and writing it up — that's
second-brain:capture - Trivially obvious or one-off details you'd never reach for again
- Project-specific context (a config value, a repo convention) — that belongs in Claude Code's own memory system, not the vault
Step 1: Load Configuration
Don't preflight-check sb availability — just run it:
npx @techpickles/sb config default && npx @techpickles/sb config vaults
If this fails, run ${CLAUDE_PLUGIN_ROOT}/scripts/diagnose-sb.sh and follow
its guidance rather than re-deriving the checks by hand.
If no default vault is configured:
Second brain not configured. Run /second-brain:setup first.
Step 2: Find or create this session's devlog note
Every entry from the current session lands in ONE note. Never create a second devlog note for a session that already has one.
Collect session context:
- Current repo and branch, if in a worktree
- Current bean ID, if applicable
- Short session id (first 8 characters of the session UUID)
- Source string format:
claude-code session {short-id} ({repo}, branch {branch}, bean {id})(omit fields that don't apply, matching the format the retireddistill-conversationcommand used)
Check your own memory first. If you already created or appended to this session's devlog note earlier in this same conversation, you already have its path in context — go straight to Step 2a and append. No CLI search needed: this is the same mechanism
second-brain:insightuses for its own accumulating session note ("if the session file already exists, was created in an earlier call in this same conversation, append").If this is the first devlog entry this session, go to Step 2b to create the note, and keep its path in mind for the rest of the session.
Fallback, only if you've genuinely lost track of the note's path (e.g. a context compaction wiped your memory of this session) — resolve a real path instead of guessing:
npx @techpickles/sb vault obsidian— parse the JSON for the top-levelinboxfield (the inbox subfolder name, e.g.📫 Inbox). This is the precomputed fieldsb note createitself resolves the inbox folder from — don't parseapp.newFileFolderPathdirectly, since that skips the Zettelkasten Prefixer plugin's folder override (inboxiszkPrefixer?.folder ?? app?.newFileFolderPath) and can point at the wrong folder.npx @techpickles/sb inbox list --detail— parse the JSON, look for the entry withtype: session-noteswhosesourcefield contains the current short session id, and take itsfilename- Combine:
~/.claude/vaults/{vault-name}/{inbox}/{filename}is the real, readable/editable path.inbox list --detailonly ever returns a bare filename, never a resolvable path on its own. - With the resolved path in hand, go to Step 2a and append. If the
inbox list --detailsearch finds no entry matching the current session id (genuinely nothing to recover), fall through to Step 2b and create the note instead.
Step 2a: Append to the existing note
Read the note, append one new bullet to the end of its body (format below) using the Edit tool. Don't touch its frontmatter.
Step 2b: Create the note
Staged and written in one unsandboxed Bash call:
STAGE=$(mktemp)
cat > "$STAGE" <<'EOF'
- {HH:MM} {entry}
EOF
npx @techpickles/sb note create \
--title "Session: {topic}" \
--source "claude-code session {short-id} ({repo}, branch {branch}, bean {id})" \
--content "$(cat "$STAGE")"
rm -f "$STAGE"
Run this with the Bash tool's dangerouslyDisableSandbox: true set from
the start, not as a retry. Two reasons, same as the retired
distill-conversation flow documented: vault paths symlink outside the
sandbox's writable allowlist (a sandboxed attempt always EPERMs), and
$TMPDIR resolves to a different path under
dangerouslyDisableSandbox: true than under the normal sandbox — staging
and reading have to happen in the same call under the same mode or the
read can miss the file. The staged file must be a fresh mktemp path,
not a fixed filename: $TMPDIR is shared across every Claude Code
session on the machine, and a fixed-name file can be overwritten or read
by a stale write from a different session.
sb note create prints JSON with a path field. If stdout has anything
before the { (e.g. a stray npm warn ... line from the caller's own
.npmrc), extract the JSON substring starting at the first { rather
than parsing the whole stdout blob.
sb note create only ever writes captured, source, repo, branch,
commit to frontmatter — it has no --type/--status flag, and repo/
branch come out as the literal none/none (devlog's --source format
doesn't match sb's own conversation:repo=...,branch=... parsing
convention). Read the file back at the path just parsed to preserve the
exact frontmatter sb wrote, then Edit it:
- add
status: raw - add
type: session-notes - replace the
repo: noneline sb wrote withrepo: {repo-name}(from git context if available) — don't add a secondrepo:line - replace the
branch: noneline sb wrote withbranch: {branch-name}(from git context if available) — don't add a secondbranch:line - add
bean: {bean-id}(from git context if available) - keep
sourceas sb wrote it — theclaude-code session ...string already covers provenance for devlog's format - keep
capturedas sb wrote it
Without this fixup, the note is invisible both to this skill's own
"check memory / find existing note" logic (Step 2, above) and to
process-inbox's stage 3a filter, which requires exactly status: raw +
type: session-notes to ever pick the note up.
Entry format
One bullet per entry, each with a wall-clock timestamp:
- {HH:MM} {what happened, why, anything surprising — one to three
sentences, standalone enough to make sense without the surrounding
conversation}
Prose, not a title. Write it like a short journal line, not a commit message.
Constraints
- One note per session — append, never create a second one for the same session.
- Never ask before writing, never present a summary and wait for a selection.
- Don't route, connect, move the note, or touch the daily note. That's
entirely
process-inbox's job, later. - Frontmatter stays
status: raw,type: session-notesfor the life of the session.process-inboxowns every status transition after that.