Skip to content

feat: add Cowork compatibility, /mode command, and make-mode skill#1309

Closed
thedotmack wants to merge 4 commits intomainfrom
feat/cowork-modes
Closed

feat: add Cowork compatibility, /mode command, and make-mode skill#1309
thedotmack wants to merge 4 commits intomainfrom
feat/cowork-modes

Conversation

@thedotmack
Copy link
Owner

Summary

  • Lazy session init: Observation endpoint auto-initializes sessions in Cowork environments where UserPromptSubmit hook doesn't fire, unblocking the entire AI processing pipeline
  • Python version fallback: ChromaMcpManager probes Python 3.13→3.10 instead of hanging 30s on unavailable versions
  • /mode command: List and switch modes dynamically via /mode and /mode <name>
  • Cowork mode: 6 observation types (creation, research, transformation, decision, discovery, automation) and 7 concepts optimized for knowledge work
  • make-mode skill: Guided workflow for creating custom modes for any domain

Test plan

  • Start a Cowork session → perform tool uses → verify ai.lastInteraction is no longer null via curl localhost:37777/api/health
  • Verify Chroma doesn't timeout on systems without Python 3.13
  • /mode lists all modes including cowork
  • /mode cowork switches and updates settings
  • /mode code switches back without regressions
  • /make-mode walks through mode creation and produces valid JSON
  • All existing code--* language variants still load correctly

🤖 Generated with Claude Code

…mode skill

- Add lazy session initialization in observation endpoint for Cowork
  environments where UserPromptSubmit hook doesn't fire
- Add Python version auto-detection in ChromaMcpManager (probes 3.13→3.10)
- Add matcher "*" to UserPromptSubmit hook for defense-in-depth
- Add /mode slash command to list and switch modes via settings API
- Add cowork.json mode with 6 knowledge-work observation types and 7 concepts
- Add cowork--chill.json behavioral variant for selective recording
- Add make-mode skill for guided custom mode creation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@claude
Copy link

claude bot commented Mar 9, 2026

PR Review: feat/cowork-modes - see full review below

@claude
Copy link

claude bot commented Mar 9, 2026

Claude-Mem: AI Development Instructions

Claude-mem is a Claude Code plugin providing persistent memory across sessions. It captures tool usage, compresses observations using the Claude Agent SDK, and injects relevant context into future sessions.

Architecture

5 Lifecycle Hooks: SessionStart → UserPromptSubmit → PostToolUse → Summary → SessionEnd

Hooks (src/hooks/*.ts) - TypeScript → ESM, built to plugin/scripts/*-hook.js

Worker Service (src/services/worker-service.ts) - Express API on port 37777, Bun-managed, handles AI processing asynchronously

Database (src/services/sqlite/) - SQLite3 at ~/.claude-mem/claude-mem.db

Search Skill (plugin/skills/mem-search/SKILL.md) - HTTP API for searching past work, auto-invoked when users ask about history

Planning Skill (plugin/skills/make-plan/SKILL.md) - Orchestrator instructions for creating phased implementation plans with documentation discovery

Execution Skill (plugin/skills/do/SKILL.md) - Orchestrator instructions for executing phased plans using subagents

Chroma (src/services/sync/ChromaSync.ts) - Vector embeddings for semantic search

Viewer UI (src/ui/viewer/) - React interface at http://localhost:37777, built to plugin/ui/viewer.html

Privacy Tags

  • <private>content</private> - User-level privacy control (manual, prevents storage)

Implementation: Tag stripping happens at hook layer (edge processing) before data reaches worker/database. See src/utils/tag-stripping.ts for shared utilities.

Build Commands

npm run build-and-sync        # Build, sync to marketplace, restart worker

Configuration

Settings are managed in ~/.claude-mem/settings.json. The file is auto-created with defaults on first run.

File Locations

  • Source: <project-root>/src/
  • Built Plugin: <project-root>/plugin/
  • Installed Plugin: ~/.claude/plugins/marketplaces/thedotmack/
  • Database: ~/.claude-mem/claude-mem.db
  • Chroma: ~/.claude-mem/chroma/

Exit Code Strategy

Claude-mem hooks use specific exit codes per Claude Code's hook contract:

  • Exit 0: Success or graceful shutdown (Windows Terminal closes tabs)
  • Exit 1: Non-blocking error (stderr shown to user, continues)
  • Exit 2: Blocking error (stderr fed to Claude for processing)

Philosophy: Worker/hook errors exit with code 0 to prevent Windows Terminal tab accumulation. The wrapper/plugin layer handles restart logic. ERROR-level logging is maintained for diagnostics.

See private/context/claude-code/exit-codes.md for full hook behavior matrix.

Requirements

  • Bun (all platforms - auto-installed if missing)
  • uv (all platforms - auto-installed if missing, provides Python for Chroma)
  • Node.js

Documentation

Public Docs: https://docs.claude-mem.ai (Mintlify)
Source: docs/public/ - MDX files, edit docs.json for navigation
Deploy: Auto-deploys from GitHub on push to main

Pro Features Architecture

Claude-mem is designed with a clean separation between open-source core functionality and optional Pro features.

Open-Source Core (this repository):

  • All worker API endpoints on localhost:37777 remain fully open and accessible
  • Pro features are headless - no proprietary UI elements in this codebase
  • Pro integration points are minimal: settings for license keys, tunnel provisioning logic
  • The architecture ensures Pro features extend rather than replace core functionality

Pro Features (coming soon, external):

  • Enhanced UI (Memory Stream) connects to the same localhost:37777 endpoints as the open viewer
  • Additional features like advanced filtering, timeline scrubbing, and search tools
  • Access gated by license validation, not by modifying core endpoints
  • Users without Pro licenses continue using the full open-source viewer UI without limitation

This architecture preserves the open-source nature of the project while enabling sustainable development through optional paid features.

Important

No need to edit the changelog ever, it's generated automatically.

@claude
Copy link

claude bot commented Mar 9, 2026

#!/usr/bin/env node

/**

  • Generate CHANGELOG.md from GitHub releases
  • Fetches all releases from GitHub and formats them into Keep a Changelog format.
    */

import { execSync } from 'child_process';
import { writeFileSync } from 'fs';

function exec(command) {
try {
return execSync(command, { encoding: 'utf-8' });
} catch (error) {
console.error(Error executing command: ${command});
console.error(error.message);
process.exit(1);
}
}

function getReleases() {
console.log('📋 Fetching releases from GitHub...');
const releasesJson = exec('gh release list --limit 1000 --json tagName,publishedAt,name');
const releases = JSON.parse(releasesJson);

// Fetch body for each release
console.log(📥 Fetching details for ${releases.length} releases...);
for (const release of releases) {
const body = exec(gh release view ${release.tagName} --json body --jq '.body').trim();
release.body = body;
}

return releases;
}

function formatDate(isoDate) {
const date = new Date(isoDate);
return date.toISOString().split('T')[0]; // YYYY-MM-DD
}

function cleanReleaseBody(body) {
// Remove the "Generated with Claude Code" footer
return body
.replace(/🤖 Generated with [Claude Code].$/s, '')
.replace(/---\n
$/s, '')
.trim();
}

function extractVersion(tagName) {
// Remove 'v' prefix from tag name
return tagName.replace(/^v/, '');
}

function generateChangelog(releases) {
console.log(📝 Generating CHANGELOG.md from ${releases.length} releases...);

const lines = [
'# Changelog',
'',
'All notable changes to this project will be documented in this file.',
'',
'The format is based on Keep a Changelog.',
'',
];

// Sort releases by date (newest first)
releases.sort((a, b) => new Date(b.publishedAt) - new Date(a.publishedAt));

for (const release of releases) {
const version = extractVersion(release.tagName);
const date = formatDate(release.publishedAt);
const body = cleanReleaseBody(release.body);

// Add version header
lines.push(`## [${version}] - ${date}`);
lines.push('');

// Add release body
if (body) {
  // Remove the initial markdown heading if it exists (e.g., "## v5.5.0 (2025-11-11)")
  const bodyWithoutHeader = body.replace(/^##?\s+v?[\d.]+.*?\n\n?/m, '');
  lines.push(bodyWithoutHeader);
  lines.push('');
}

}

return lines.join('\n');
}

function main() {
console.log('🔧 Generating CHANGELOG.md from GitHub releases...\n');

const releases = getReleases();

if (releases.length === 0) {
console.log('⚠️ No releases found');
return;
}

const changelog = generateChangelog(releases);

writeFileSync('CHANGELOG.md', changelog, 'utf-8');

console.log('\n✅ CHANGELOG.md generated successfully!');
console.log( ${releases.length} releases processed);
}

main();

- New set-mode skill: full mode activation (settings + project
  instructions + worker restart) in one command
- Updated make-mode skill: added Step 6 for creating mode CLAUDE.md
  files focused on human-primary agent relationship
- Mode CLAUDE.md files are standalone project instructions with no
  claude-mem internal references
- set-mode installs mode instructions via <claude-mem-mode> tags
  in the project's CLAUDE.md (idempotent on mode switches)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@claude
Copy link

claude bot commented Mar 9, 2026

REVIEW PART 1/4 - Bugs

  1. Race condition in lazy session init (SessionRoutes.ts): Two concurrent observations arriving milliseconds apart could both pass the getSession check and both call initializeSession, resulting in a duplicate session or overwritten state. Consider using a Set of in-flight session IDs as a lightweight lock, or check-and-init inside the session manager atomically.

  2. createSDKSession called unconditionally: createSDKSession is called on every observation request regardless of whether the session already exists. If not idempotent (inserts a new DB row each call), phantom DB records will accumulate. Confirm this is safe or gate the DB call behind the session check.

  3. Synthetic prompt saved to the DB: The string [auto-initialized from observation] is stored via saveUserPrompt and will appear in memory search results and session history. Consider skipping saveUserPrompt for auto-init sessions, or using a flag so search/UI can filter it out.

@claude
Copy link

claude bot commented Mar 9, 2026

REVIEW PART 2/4 - Performance and Security

Performance:

  1. Blocking execSync loop in detectPythonVersion() (ChromaMcpManager.ts): With 4 versions x 5s timeout each, worst-case startup cost is 20 seconds of blocking the event loop. The PR description says it fixes the 30s hang, but 20s is still significant. Consider: (a) reducing per-probe timeout to 1-2s since a python --version call that takes more than a second is almost certainly unavailable, or (b) running probes in parallel with Promise.all + execAsync.

  2. detectPythonVersion() result not cached: If ChromaMcpManager is instantiated more than once, it will re-probe Python each time. Cache at the module level or as a private static field (e.g. private static cachedPythonVersion: string | undefined).

Security:

  1. Path traversal risk in /mode command (plugin/commands/mode.md): The validation step constructs a filesystem path from raw user input: plugin/modes/ARGUMENTS.json. A value like ../../.ssh/id_rsa would traverse the directory. Since this is a command spec for Claude to follow, add an explicit sanitization instruction -- verify the argument contains only alphanumeric characters, hyphens, and underscores before any filesystem or network use.

@claude
Copy link

claude bot commented Mar 9, 2026

REVIEW PART 3/4 - Code Quality and Nits

Code Quality / Minor:

  1. hooks.json matcher change needs clearer documentation: Adding matcher: star to UserPromptSubmit should be explicitly called out. If the hook previously fired without a matcher this is a no-op and should be noted. If not, it is a meaningful behavioral change that warrants its own test step.

  2. /mode command next-session caveat buries the lede: The note that a mode change only takes effect after /clear or a new conversation is critical UX info. Consider surfacing it more prominently in the confirmation message template.

  3. make-mode skill requires 33 prompt fields -- fragile: If even one field is omitted, the generated mode JSON will be invalid. This is a high bar for AI-generated output. Consider providing a JSON schema or template that modes inherit from, with only domain-specific fields requiring generation.

Nits:

  • cowork--chill.json intentionally omits observation_types/observation_concepts (delegating to parent), but this pattern is not documented in the make-mode skill. Worth adding a note that behavioral variants can omit these fields.
  • getProjectName(cwd) is newly imported -- confirm it handles undefined/empty cwd gracefully for environments where cwd is not passed in the observation payload.

@claude
Copy link

claude bot commented Mar 9, 2026

REVIEW PART 4/4 - Overall Summary

The feature set is solid and the Cowork mode design is thoughtful. The lazy session init is the most impactful fix and the core logic is clear.

Main asks before merge:

  1. Address the race condition in lazy session init (SessionRoutes.ts)
  2. Confirm createSDKSession idempotency before the lazy-init guard
  3. Reduce the Python probe timeout in detectPythonVersion() -- 20s worst-case is still too long
  4. Add input sanitization note to the /mode command to prevent path traversal

Lower priority:

  • Cache detectPythonVersion() result
  • Filter synthetic [auto-initialized from observation] prompts from search/history
  • Document the behavioral variant (--chill) pattern in make-mode skill
  • Clarify that hooks.json matcher change is intentional/no-op

@claude
Copy link

claude bot commented Mar 9, 2026

test

@claude
Copy link

claude bot commented Mar 9, 2026

Claude-Mem: AI Development Instructions

Claude-mem is a Claude Code plugin providing persistent memory across sessions. It captures tool usage, compresses observations using the Claude Agent SDK, and injects relevant context into future sessions.

Architecture

5 Lifecycle Hooks: SessionStart → UserPromptSubmit → PostToolUse → Summary → SessionEnd

Hooks (src/hooks/*.ts) - TypeScript → ESM, built to plugin/scripts/*-hook.js

Worker Service (src/services/worker-service.ts) - Express API on port 37777, Bun-managed, handles AI processing asynchronously

Database (src/services/sqlite/) - SQLite3 at ~/.claude-mem/claude-mem.db

Search Skill (plugin/skills/mem-search/SKILL.md) - HTTP API for searching past work, auto-invoked when users ask about history

Planning Skill (plugin/skills/make-plan/SKILL.md) - Orchestrator instructions for creating phased implementation plans with documentation discovery

Execution Skill (plugin/skills/do/SKILL.md) - Orchestrator instructions for executing phased plans using subagents

Chroma (src/services/sync/ChromaSync.ts) - Vector embeddings for semantic search

Viewer UI (src/ui/viewer/) - React interface at http://localhost:37777, built to plugin/ui/viewer.html

Privacy Tags

  • <private>content</private> - User-level privacy control (manual, prevents storage)

Implementation: Tag stripping happens at hook layer (edge processing) before data reaches worker/database. See src/utils/tag-stripping.ts for shared utilities.

Build Commands

npm run build-and-sync        # Build, sync to marketplace, restart worker

Configuration

Settings are managed in ~/.claude-mem/settings.json. The file is auto-created with defaults on first run.

File Locations

  • Source: <project-root>/src/
  • Built Plugin: <project-root>/plugin/
  • Installed Plugin: ~/.claude/plugins/marketplaces/thedotmack/
  • Database: ~/.claude-mem/claude-mem.db
  • Chroma: ~/.claude-mem/chroma/

Exit Code Strategy

Claude-mem hooks use specific exit codes per Claude Code's hook contract:

  • Exit 0: Success or graceful shutdown (Windows Terminal closes tabs)
  • Exit 1: Non-blocking error (stderr shown to user, continues)
  • Exit 2: Blocking error (stderr fed to Claude for processing)

Philosophy: Worker/hook errors exit with code 0 to prevent Windows Terminal tab accumulation. The wrapper/plugin layer handles restart logic. ERROR-level logging is maintained for diagnostics.

See private/context/claude-code/exit-codes.md for full hook behavior matrix.

Requirements

  • Bun (all platforms - auto-installed if missing)
  • uv (all platforms - auto-installed if missing, provides Python for Chroma)
  • Node.js

Documentation

Public Docs: https://docs.claude-mem.ai (Mintlify)
Source: docs/public/ - MDX files, edit docs.json for navigation
Deploy: Auto-deploys from GitHub on push to main

Pro Features Architecture

Claude-mem is designed with a clean separation between open-source core functionality and optional Pro features.

Open-Source Core (this repository):

  • All worker API endpoints on localhost:37777 remain fully open and accessible
  • Pro features are headless - no proprietary UI elements in this codebase
  • Pro integration points are minimal: settings for license keys, tunnel provisioning logic
  • The architecture ensures Pro features extend rather than replace core functionality

Pro Features (coming soon, external):

  • Enhanced UI (Memory Stream) connects to the same localhost:37777 endpoints as the open viewer
  • Additional features like advanced filtering, timeline scrubbing, and search tools
  • Access gated by license validation, not by modifying core endpoints
  • Users without Pro licenses continue using the full open-source viewer UI without limitation

This architecture preserves the open-source nature of the project while enabling sustainable development through optional paid features.

Important

No need to edit the changelog ever, it's generated automatically.

@claude
Copy link

claude bot commented Mar 9, 2026

Review summary: Good foundational work on Cowork compatibility, Python version fallback, and mode system. Key items to address: (1) ChromaMcpManager.ts detectPythonVersion() - uses execSync synchronously, blocking the event loop up to 20s; result not memoized, re-probes all versions on every call; cache in a private field. Fallback to '3.13' still hangs - use python3 or python (unversioned) instead. (2) SessionRoutes.ts lazy init - hardcoded prompt number 1 could duplicate or misalign if UserPromptSubmit already fired; TOCTOU race between getSession and initializeSession could double-initialize under concurrent Cowork requests. (3) mode.md/set-mode SKILL.md - ARGUMENTS interpolated directly into curl JSON body; mode names with metacharacters break it; use jq with named args. (4) /mode vs /set-mode UX inconsistency - /mode says next session, /set-mode says active now; merge or clearly differentiate. (5) worker-service.cjs is a compiled artifact in the diff - confirm it was rebuilt from updated source. Minor: cowork--chill.json parent-fallback logic needs integration test; matcher wildcard in hooks.json is correct; cowork.json taxonomy is well-structured.

@claude
Copy link

claude bot commented Mar 9, 2026

FULL_REVIEW_ENCODED_PLACEHOLDER

@claude
Copy link

claude bot commented Mar 9, 2026

Code Review - Placeholder

@claude
Copy link

claude bot commented Mar 9, 2026

Claude-Mem: AI Development Instructions

Claude-mem is a Claude Code plugin providing persistent memory across sessions. It captures tool usage, compresses observations using the Claude Agent SDK, and injects relevant context into future sessions.

Architecture

5 Lifecycle Hooks: SessionStart → UserPromptSubmit → PostToolUse → Summary → SessionEnd

Hooks (src/hooks/*.ts) - TypeScript → ESM, built to plugin/scripts/*-hook.js

Worker Service (src/services/worker-service.ts) - Express API on port 37777, Bun-managed, handles AI processing asynchronously

Database (src/services/sqlite/) - SQLite3 at ~/.claude-mem/claude-mem.db

Search Skill (plugin/skills/mem-search/SKILL.md) - HTTP API for searching past work, auto-invoked when users ask about history

Planning Skill (plugin/skills/make-plan/SKILL.md) - Orchestrator instructions for creating phased implementation plans with documentation discovery

Execution Skill (plugin/skills/do/SKILL.md) - Orchestrator instructions for executing phased plans using subagents

Chroma (src/services/sync/ChromaSync.ts) - Vector embeddings for semantic search

Viewer UI (src/ui/viewer/) - React interface at http://localhost:37777, built to plugin/ui/viewer.html

Privacy Tags

  • <private>content</private> - User-level privacy control (manual, prevents storage)

Implementation: Tag stripping happens at hook layer (edge processing) before data reaches worker/database. See src/utils/tag-stripping.ts for shared utilities.

Build Commands

npm run build-and-sync        # Build, sync to marketplace, restart worker

Configuration

Settings are managed in ~/.claude-mem/settings.json. The file is auto-created with defaults on first run.

File Locations

  • Source: <project-root>/src/
  • Built Plugin: <project-root>/plugin/
  • Installed Plugin: ~/.claude/plugins/marketplaces/thedotmack/
  • Database: ~/.claude-mem/claude-mem.db
  • Chroma: ~/.claude-mem/chroma/

Exit Code Strategy

Claude-mem hooks use specific exit codes per Claude Code's hook contract:

  • Exit 0: Success or graceful shutdown (Windows Terminal closes tabs)
  • Exit 1: Non-blocking error (stderr shown to user, continues)
  • Exit 2: Blocking error (stderr fed to Claude for processing)

Philosophy: Worker/hook errors exit with code 0 to prevent Windows Terminal tab accumulation. The wrapper/plugin layer handles restart logic. ERROR-level logging is maintained for diagnostics.

See private/context/claude-code/exit-codes.md for full hook behavior matrix.

Requirements

  • Bun (all platforms - auto-installed if missing)
  • uv (all platforms - auto-installed if missing, provides Python for Chroma)
  • Node.js

Documentation

Public Docs: https://docs.claude-mem.ai (Mintlify)
Source: docs/public/ - MDX files, edit docs.json for navigation
Deploy: Auto-deploys from GitHub on push to main

Pro Features Architecture

Claude-mem is designed with a clean separation between open-source core functionality and optional Pro features.

Open-Source Core (this repository):

  • All worker API endpoints on localhost:37777 remain fully open and accessible
  • Pro features are headless - no proprietary UI elements in this codebase
  • Pro integration points are minimal: settings for license keys, tunnel provisioning logic
  • The architecture ensures Pro features extend rather than replace core functionality

Pro Features (coming soon, external):

  • Enhanced UI (Memory Stream) connects to the same localhost:37777 endpoints as the open viewer
  • Additional features like advanced filtering, timeline scrubbing, and search tools
  • Access gated by license validation, not by modifying core endpoints
  • Users without Pro licenses continue using the full open-source viewer UI without limitation

This architecture preserves the open-source nature of the project while enabling sustainable development through optional paid features.

Important

No need to edit the changelog ever, it's generated automatically.

@claude
Copy link

claude bot commented Mar 9, 2026

test

@claude
Copy link

claude bot commented Mar 9, 2026

Code Review

Good foundational work. The Cowork compatibility, Python version fallback, and mode system are useful additions. A few items to address before merging:


ChromaMcpManager.tsdetectPythonVersion()

Blocking call on hot path. detectPythonVersion() uses execSync synchronously from buildCommandArgs(). Worst case (no
Python installed) this blocks the event loop for up to 20s (5000ms × 4 versions).

No caching. The result is not memoized — every call to buildCommandArgs() re-probes all versions. Cache it in a private field:

private cachedPythonVersion: string | undefined;

private detectPythonVersion(): string {
  if (this.cachedPythonVersion) return this.cachedPythonVersion;
  // ... probe logic
  this.cachedPythonVersion = found;
  return found;
}

Fallback still hangs. When no Python 3.10–3.13 is found, it returns '3.13' — the original bug causing 30s hangs. Consider falling back to python3 or python (unversioned) rather than a version-pinned fallback that will fail the same way.


SessionRoutes.ts — lazy init

Hardcoded prompt number. The lazy init calls store.saveUserPrompt(contentSessionId, 1, syntheticPrompt) with a hardcoded 1. If an observation arrives mid-session after a real UserPromptSubmit already fired, this could duplicate prompt #1 or misalign the sequence. Consider deriving the prompt number from store.getPromptNumberFromUserPrompts(contentSessionId) or using a sentinel clearly marked as a lazy-init artifact.

Potential race condition. There is a TOCTOU window between this.sessionManager.getSession(sessionDbId) and this.sessionManager.initializeSession(...). Under concurrent rapid tool use in Cowork, two requests could both pass the null check and double-initialize. Worth verifying initializeSession / ensureGeneratorRunning are idempotent.


mode.md / set-mode/SKILL.md — argument injection

$ARGUMENTS is interpolated directly into a curl JSON body. A mode name containing " or other JSON metacharacters would produce malformed or manipulated JSON. Risk is low (localhost API), but consider using jw -n --arg to build the payload safely.


UX inconsistency: /mode vs /set-mode

/mode <name> tells the user the change takes effect on the next session (after /clear). /set-mode <name> restarts the worker immediately and says the mode is active now. This creates a confusing two-path experience. Either merge them or clearly label /mode as lightweight (settings only) and /set-mode as full setup (settings + project instructions + worker restart).


plugin/scripts/worker-service.cjs in the diff

This is a compiled artifact. Reviewing it is noisy and error-prone. Worth noting explicitly in the PR description whether it was freshly rebuilt from the updated TypeScript source.


Minor

  • cowork--chill.json only overrides two prompt fields — the parent-fallback logic for observation_types/observation_concepts needs to be consistent between /mode and /set-mode. An integration test for /mode cowork--chill inheriting cowork observation types would catch regressions here.
  • The "matcher": "*" addition to hooks.json for UserPromptSubmit is correct — no issues.
  • cowork.json is well-structured. The six-type taxonomy (creation, research, transformation, decision, discovery, automation) covers the knowledge-work space cleanly.

Summary: Lazy session init and Python version fallback are solid fixes. Main items to address: cache detectPythonVersion() and bound its worst-case blocking time, nail down the hardcoded prompt number in lazy init, and align the /mode vs /set-mode UX story.

@claude
Copy link

claude bot commented Mar 9, 2026

Code Review

Good foundational work. The Cowork compatibility, Python version fallback, and mode system are useful additions. A few items to address before merging:


ChromaMcpManager.tsdetectPythonVersion()

Blocking call on hot path. detectPythonVersion() uses execSync synchronously from buildCommandArgs(). Worst case (noPython installed) this blocks the event loop for up to 20s (5000ms × 4 versions).

No caching. The result is not memoized — every call to buildCommandArgs() re-probes all versions. Cache it in a private field:

private cachedPythonVersion: string | undefined;

private detectPythonVersion(): string {
  if (this.cachedPythonVersion) return this.cachedPythonVersion;
  // ... probe logic
  this.cachedPythonVersion = found;
  return found;
}

Fallback still hangs. When no Python 3.10–3.13 is found, it returns '3.13' — the original bug causing 30s hangs. Consider falling back to python3 or python (unversioned) rather than a version-pinned fallback that will fail the same way.


SessionRoutes.ts — lazy init

Hardcoded prompt number. The lazy init calls store.saveUserPrompt(contentSessionId, 1, syntheticPrompt) with a hardcoded 1. If an observation arrives mid-session after a real UserPromptSubmit already fired, this could duplicate prompt#1 or misalign the sequence. Consider deriving the prompt number from store.getPromptNumberFromUserPrompts(contentSessionId) or using a sentinel clearly marked as a lazy-init artifact.

Potential race condition. There is a TOCTOU window between this.sessionManager.getSession(sessionDbId) and this.sessionManager.initializeSession(...). Under concurrent rapid tool use in Cowork, two requests could both pass the null check and double-initialize. Worth verifying initializeSession / ensureGeneratorRunning are idempotent.


mode.md / set-mode/SKILL.md — argument injection

$ARGUMENTS is interpolated directly into a curl JSON body. A mode name containing " or other JSON metacharacters would produce malformed or manipulated JSON. Risk is low (localhost API), but consider using jq -n --arg to build the payload safely.


UX inconsistency: /mode vs /set-mode

/mode <name> tells the user the change takes effect on the next session (after /clear). /set-mode <name> restarts the worker immediately and says the mode is active now. This creates a confusing two-path experience. Either merge them or clearly label /mode as lightweight (settings only) and /set-mode as full setup (settings + project instructions + worker restart).


plugin/scripts/worker-service.cjs in the diff

This is a compiled artifact. Reviewing it is noisy and error-prone. Worth noting explicitly in the PR description whether it was freshly rebuilt from the updated TypeScript source.


Minor

  • cowork--chill.json only overrides two prompt fields — the parent-fallback logic for observation_types/observation_concepts needs to be consistent between /mode and /set-mode. An integration test for /mode cowork--chill inheriting cowork observation types would catch regressions here.
  • The "matcher": "*" addition to hooks.json for UserPromptSubmit is correct — no issues.
  • cowork.json is well-structured. The six-type taxonomy (creation, research, transformation, decision, discovery, automation) covers the knowledge-work space cleanly.

Summary: Lazy session init and Python version fallback are solid fixes. Main items to address: cache detectPythonVersion() and bound its worst-case blocking time, nail down the hardcoded prompt number in lazy init, and align the /mode vs /set-mode UX story.

@claude
Copy link

claude bot commented Mar 9, 2026

test comment

… sync

Reverted ChromaMcpManager.ts python detection change (unrelated to cowork).
Rebuilt worker-service.cjs from clean source without pro code contamination.
Added --ignore-scripts to bun install in sync-marketplace to stop
tree-sitter-cli from re-downloading its binary on every sync.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@claude
Copy link

claude bot commented Mar 9, 2026

test

@claude
Copy link

claude bot commented Mar 9, 2026

placeholder test

@claude
Copy link

claude bot commented Mar 9, 2026

test comment 2

@claude
Copy link

claude bot commented Mar 9, 2026


PR Review: feat: add Cowork compatibility, /mode command, and make-mode skill

Overall this is a well-scoped PR that delivers real value. A few things worth addressing before merge.

Bugs / Correctness

  1. detectPythonVersion() called on every buildCommandArgs() - potential 20s startup penalty

src/services/sync/ChromaMcpManager.ts - detectPythonVersion() is invoked synchronously inside buildCommandArgs() with no caching. In the worst case (no Python 3.10-3.13 installed), this runs 4 x 5s execSync calls = up to 20 seconds of blocking before Chroma can start. On happy paths it is fast, but on misconfigured systems the worker will feel frozen.

Suggestion: cache the result in a private field (cachedPythonVersion) so probing only happens once per process lifetime.

  1. Lazy session init stores a synthetic user prompt - may corrupt prompt numbering

In src/services/worker/http/routes/SessionRoutes.ts, store.saveUserPrompt persists the string '[auto-initialized from observation]' to user_prompts with promptNumber = 1. Then store.getPromptNumberFromUserPrompts(contentSessionId) immediately returns 1 (from the synthetic save), so the real observation gets associated with the same prompt number as the ghost. Downstream code correlating observations to user prompts by number will see phantom entries.

Suggestion: avoid writing to user_prompts for the synthetic init, or use a sentinel prompt number (e.g., 0) that downstream logic can filter.

  1. Race condition in lazy init - concurrent observations can double-initialize

Two observations arriving simultaneously for a new session can both pass the getSession check before either completes, resulting in duplicate init, duplicate prompts saved, and potentially two generator processes spinning up.

Suggestion: a simple mutex/flag keyed by sessionDbId would prevent this.

Design / Architecture

  1. Two commands that switch modes with different semantics

"/mode " and "/set-mode " both switch modes, but "/set-mode" additionally installs a mode CLAUDE.md and restarts the worker. cowork-modes.mdx tells users to use "/set-mode cowork", but plugin/commands/mode.md also allows "/mode cowork". A user running "/mode cowork" gets settings updated but no CLAUDE.md installed and no worker restart - they may think the mode is fully active when it is not.

Suggestion: have "/mode " note that "/set-mode " is the full setup path, or document clearly in the "/mode" output that it is a lightweight switch.

  1. cowork--chill.json silently inherits types/concepts with no hint in the file

The inheritance fallback lives only in the "/mode" and "/set-mode" skills. If any TypeScript code ever reads the mode JSON directly expecting these fields, it will silently get nothing. A comment in cowork--chill.json noting the inheritance intent would protect future maintainers.

Minor

  1. make-mode SKILL.md hard-codes "33 prompt fields" - if ModePrompts schema changes, this count becomes misleading. "All fields in the template" would age better.

  2. hooks.json - "matcher": "*" added without explanation - was this a bug fix (hook wasn't firing without it) or a no-op for backward compat? A brief note in the PR body would help reviewers track intent.

Positive Highlights

  • Python version probing is a real fix - the 30s hang was a genuine UX problem
  • make-mode SKILL.md distinction between JSON config (observer agent) and CLAUDE.md (primary agent) is clearly articulated
  • Cowork mode JSON is well-designed: 6 types is right at the ceiling for avoiding decision fatigue
  • cowork-modes.mdx is excellent for non-technical users; the FAQ section is particularly well-targeted

Generated with Claude Code

@thedotmack thedotmack closed this Mar 9, 2026
Sign up for free to 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