-
-
Notifications
You must be signed in to change notification settings - Fork 0
fix: multi-worktree sync race condition (#28) #34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Complete specification for GitHub Issue #13: Multi-domain memory storage to distinguish between user-level (global) and project-level context. Specification includes: - REQUIREMENTS.md: 13 functional requirements (6 P0, 4 P1, 3 P2) - ARCHITECTURE.md: 9 component designs with data flow diagrams - IMPLEMENTATION_PLAN.md: 5 phases, 24 tasks - DECISIONS.md: 7 Architecture Decision Records - CHANGELOG.md: Specification evolution history Key decisions: - User memories in separate bare git repo (~/.local/share/memory-plugin/user-memories/) - Project memories override user memories on conflict - Team domain deferred to v2 - Optional remote auto-sync (opt-in via env vars) Closes #13 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Complete specification for LLM-powered subconsciousness layer: - REQUIREMENTS.md: 23 functional requirements (P0/P1/P2), success metrics - ARCHITECTURE.md: 7 components, schema extensions, LLM output templates - IMPLEMENTATION_PLAN.md: 6 phases, 85 tasks, verification gates - DECISIONS.md: 13 Architecture Decision Records Key features: - Provider-agnostic LLM client (Anthropic, OpenAI, Ollama) - Implicit capture with confidence thresholds - Semantic linking with 5 relationship types - Memory decay and intelligent forgetting - Consolidation into meta-memories - Proactive surfacing with rate limiting Grounded in CognitiveSubstrate research (Dual-Process Theory, SOAR/ACT-R). Closes #11 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Create PROGRESS.md with 24 tasks across 5 phases - Update README.md with approval and start timestamps - Update CHANGELOG.md with approval entry 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Task 1.1 complete - Add Domain Enum to Config Changes: - Add Domain enum with USER and PROJECT values - Add get_user_memories_path() for user-memories bare repo location - Add get_user_index_path() for user-domain SQLite index - Both functions support optional ensure_exists parameter for lazy dir creation - Add comprehensive tests for new configuration (17 tests) All 1851 tests pass, 88% coverage. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add domain awareness to the Memory model for multi-domain storage: - Add `domain: str` field with default "project" for backward compatibility - Add `domain_enum` property to get Domain enum value - Add `is_user_domain` and `is_project_domain` convenience properties - Add domain properties to MemoryResult for uniform access - Add 7 tests covering domain field and properties Uses string storage for SQLite/JSON serialization compatibility, with enum property for type-safe programmatic access. Part of: SPEC-2025-12-25-001 (Multi-Domain Memories) Resolves: Task 1.2 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Phase 1 Foundation complete for multi-domain memories: Schema Migration (Task 1.3): - Bump SCHEMA_VERSION to 3 - Add domain TEXT column with 'project' default - Add idx_memories_domain index - Implement migration 3 for existing databases IndexService Domain (Task 1.4): - Update insert(), insert_batch(), update() for domain field - Update _row_to_memory() with backward-compatible domain reading Tests: - Add TestSchemaMigration class with v2→v3 migration test - Add fresh database schema test - Add domain-specific insert tests Closes phase 1 of SPEC-2025-12-25-001. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…s 2.1-2.4) - Add GitOps.for_domain(domain) class method for domain-specific instances - Implement domain instance caching with clear_domain_cache() for testing - USER domain uses user-memories path, PROJECT domain uses repo path - Add ensure_user_repo_initialized() class method - Creates bare git repo at ~/.local/share/memory-plugin/user-memories/ - Configures git identity and creates initial empty commit - Add is_bare_repository() method for verification - Add optional domain parameter to search_vector() - Add optional domain parameter to search_text() - Both methods filter by domain when specified, backward compatible - Add domain filter to get_by_spec(), get_by_namespace(), list_recent() - Add domain filter to count() method - Update get_stats() to include by_domain breakdown - Add by_domain field and by_domain_dict property to IndexStats model Tests: 14 new GitOps tests, 13 new domain filter tests (1885 total, 89% coverage) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Mark Tasks 2.1-2.4 as done in IMPLEMENTATION_PLAN.md - Update Phase 2 status to 100% done in PROGRESS.md - Add session notes for Phase 2 completion - Update current_phase to 2 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Apply 8 high-priority fixes from comprehensive code review: Security: - CRIT-001: Add ThreadPoolExecutor timeout (30s/120s) to embedding ops - SEC-HIGH-001: Add symlink verification before user-memories git init - SEC-HIGH-002: Add 64KB YAML size limit to prevent billion laughs - SEC-HIGH-003: Implement PID-based stale lock detection Performance: - PERF-HIGH-001: Add pagination (limit/offset) to get_all_ids() - PERF-HIGH-004: Add composite indexes for common query patterns Code Quality: - CRIT-003: Remove unused thread lock, document SQLite WAL concurrency - QUAL-HIGH-001: Replace 10 broad exception handlers with specific types Also removes code-review reports from docs/ (not needed in repo). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add noqa: ARG005 comments for unused lambda arguments in test mocks. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add optional SecretsFilteringService integration to CaptureService - Filter both summary and content fields before storage - Rename MemoryError to MemoryPluginError (avoid shadowing builtin) - Add configurable secrets service injection - Replace suppress(Exception) with proper try-except logging - Add lock cleanup warning for debugging
* feat: initialize observability instrumentation implementation - Create PROGRESS.md with 29 tasks across 6 phases - Update README.md with started timestamp - Spec approved and ready for implementation Implements: SPEC-2025-12-25-001 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(observability): implement Phase 1 core infrastructure Complete observability foundation with: Core Modules: - config.py: ObservabilityConfig with env-based configuration - metrics.py: Thread-safe MetricsCollector (counters, histograms, gauges) - tracing.py: Span context with contextvars-based propagation - session.py: SessionIdentifier with privacy-preserving hashes - decorators.py: measure_duration decorator (sync/async support) - logging.py: StructuredLogger with JSON/text formatters Exporters: - prometheus.py: Prometheus text format (no external deps) - json_exporter.py: Full JSON export for metrics and traces Key Features: - Lazy imports via __getattr__ for hook performance (<5ms import) - Thread-safe operations with threading.Lock - Privacy-preserving SHA256 hashes (8 char truncated) - Histogram buckets aligned with hook timeouts (2s, 5s, 15s, 30s) - Rolling window for bounded memory in histograms Tests: - 115 new tests across 8 test modules - Coverage: 87.76% (above 80% threshold) - All quality gates passing Closes Phase 1 of SPEC-2025-12-25-001 (Observability Instrumentation) Ref: #10 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: Complete observability instrumentation (Phases 1-4) Implement comprehensive observability for git-notes-memory plugin: Phase 1 - Core Infrastructure: - MetricsCollector with counters, histograms, gauges (thread-safe) - SpanContext and trace_operation for distributed tracing - StructuredLogger with JSON/text formatters - SessionIdentifier for multi-tenant distinguishability - measure_duration decorator and timed_context Phase 2 - Service Instrumentation: - Instrument CaptureService, RecallService, EmbeddingService - Instrument IndexService, GitOps, Hook Handlers - Fix silent failure points with explicit logging and metrics - Add timed_hook_execution context manager Phase 3 - Structured Logging: - Add *args support to StructuredLogger for backwards compatibility - Migrate all 5 hook handlers to get_logger() - Migrate 7 hook support modules to structured logging Phase 4 - CLI & Export: - /memory:metrics command with text/json/prometheus formats - /memory:traces command with filtering options - /memory:health command with --timing for latency percentiles - PrometheusExporter class for Prometheus text format Phases 5-6 (OpenTelemetry, Docker stack) skipped as optional Tier 3. Closes #10 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: add exponential backoff and observability documentation MAXALL deep-clean remediation: - CRIT-001: Add exponential backoff with jitter to lock acquisition - Replaces fixed 100ms retry with 50ms→2s exponential backoff - Adds 0-10% jitter to prevent thundering herd - Reduces resource exhaustion risk under high concurrency - HIGH-004: Add composite index on (namespace, timestamp DESC) - Improves range query performance for get_by_namespace() - HIGH-014: Create docs/observability.md documentation - Metrics collection and Prometheus export - Tracing with span hierarchies - Structured logging configuration - Health checks and performance characteristics Code review artifacts in docs/code-review/2025/12/25/: - CODE_REVIEW.md: 90 findings from 9 specialist agents - REVIEW_SUMMARY.md: Executive summary - REMEDIATION_TASKS.md: Actionable checklist - REMEDIATION_REPORT.md: Final status with false positives All 1949 tests passing, 87.70% coverage. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(observability): add Docker Compose local observability stack Phase 6 implementation (tasks 6.1-6.2): - docker-compose.yml with OTEL Collector, Prometheus, Tempo, Loki, Grafana - OpenTelemetry Collector config with OTLP receivers and exporters - Prometheus scrape configuration for OTEL metrics - Tempo distributed tracing backend configuration - Loki log aggregation configuration - Grafana datasource provisioning (Prometheus, Tempo, Loki) - Grafana dashboard provisioning with auto-discovery - memory-operations.json dashboard (captures, searches, latency) - hook-performance.json dashboard (executions, timeouts, p95 latency) Exposed ports: - 3000: Grafana UI - 9090: Prometheus UI - 4317/4318: OTEL gRPC/HTTP - 3100: Loki - 3200: Tempo Usage: cd docker && docker-compose up -d 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(security): divest inline Python from command files into scripts Addresses PR #24 review comments: - SECURITY: Moved inline Python code from commands/*.md to scripts/*.py to prevent command injection via $ARGUMENTS string interpolation - scripts/metrics.py: Safe argument parsing via argparse/sys.argv - scripts/traces.py: Safe argument parsing via argparse/sys.argv - scripts/health.py: Safe argument parsing via argparse/sys.argv - Updated commands/metrics.md, traces.md, health.md to call scripts - Fixed exporters/__init__.py: explicit imports instead of lazy loading to resolve Copilot undefined export warnings The previous implementation embedded $ARGUMENTS directly into Python string literals, allowing code injection via crafted arguments. The new scripts pass arguments safely via command line. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(observability): implement OTLP HTTP exporter for telemetry push Completes Phase 5 of the observability spec by adding OTLP HTTP export capability so telemetry can be pushed to OpenTelemetry Collector and the Docker Compose observability stack. Changes: - New `exporters/otlp.py` with OTLPExporter class using stdlib only - Converts internal Span/metrics to OTLP JSON format - Pushes to {endpoint}/v1/traces and {endpoint}/v1/metrics - Configurable via MEMORY_PLUGIN_OTLP_ENDPOINT env var - Updated Stop handler to flush telemetry at session end - Updated observability.md with OTLP configuration section - Added comprehensive tests for OTLP exporter - pyproject.toml: added S310 per-file-ignore for OTLP file The exporter uses urllib.request (stdlib) to avoid adding dependencies. When MEMORY_PLUGIN_OTLP_ENDPOINT is set, traces and metrics are automatically exported when the Claude Code session ends. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Fix connection leak on index initialization failure (CRIT-001) - Fix connection cleanup in session start hook (MED-001) - Add missing domain parameter to Memory creation in capture - Add composite index for status+timestamp queries (LOW-004)
- Add logger import to git_ops module - Log warning with exception details when batch fetch fails (LOW-010)
- Document all core, hook, secrets, and observability config options - Include example configurations for team, security, and debug modes - Reference remote sync and OpenTelemetry integration settings
- Add security-scan-on-edit hook for bandit security checks - Fix hook paths from git-notes-memory-manager to git-notes-memory - Update descriptions to reference CI workflow equivalents - Document all hooks in CLAUDE.md with CI mapping
- CODE_REVIEW.md: Full review report from 9 specialist agents - REMEDIATION_TASKS.md: Prioritized task list (all completed)
Add RULE 5 to all guidance templates (minimal, standard, detailed) that encourages Claude to search for relevant memories BEFORE modifying code, rather than relying solely on the ~5 generic memories injected at session start. Key additions: - RULE 5: PROACTIVE MEMORY SEARCH section with when/how to search - Failure mode warning for skipping proactive search - Self-audit reminder to search before major changes - Context explaining SessionStart relevance (~0.53) vs task-specific (~0.77+) This improves memory utilization by encouraging task-specific queries that yield much higher relevance scores than the generic project name search used at session initialization. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Use "./" prefix for source path (schema requirement) - Remove published plugin metadata (belongs in plugin.json) - Simplify to essential fields for local development install 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Implements Issue #11 subconsciousness layer with deep-clean remediation. - LLM client with circuit breaker pattern (3 states: CLOSED/OPEN/HALF_OPEN) - Multi-provider support (Anthropic, OpenAI, Ollama) - Implicit capture service for auto-detecting memory-worthy content - Adversarial prompt detection for security - Rate limiting with token bucket algorithm - Transcript chunking for large sessions Critical: - CRIT-001: Circuit breaker for LLM provider calls - CRIT-002: ServiceRegistry pattern replacing global mutable state High: - HIGH-001: Term limit (100) for O(n²) pattern matching - HIGH-002: sqlite-vec UPSERT limitation documented - HIGH-003: Composite index for common query pattern - HIGH-007: Jitter in exponential backoff - HIGH-008: PII scrubbing with 7 pattern types Medium: - MED-004: ANALYZE after VACUUM - MED-005: Context manager for SQLite connection - MED-007: Magic numbers to named constants - MED-008: Stale lock detection (5-minute threshold) - MED-011: Consent mechanism for PreCompact auto-capture - 2191 tests passing - 80.72% coverage - mypy --strict clean - ruff check clean 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix command injection vulnerability in commands/review.md by passing capture ID via environment variable instead of shell interpolation - Add explanatory comment to exception handler in implicit_capture_agent.py Security: - CVE-class shell injection fixed in --approve and --reject paths 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Project completed successfully with Phase 1-2 delivered: - LLM Foundation (provider-agnostic client) - Implicit Capture (auto-extraction with confidence scoring) Deliverables: - 134 tests (87%+ coverage) - 13 ADRs - Security fix (command injection) - PR #26 (open, ready for merge) Effort: ~14 hours (planned: 80-100 hours, -86% under budget) Scope: Phases 1-2 complete, Phases 3-6 deferred Artifacts moved to: docs/spec/completed/2025-12-25-llm-subconsciousness/ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Critical fixes (4): - CRIT-001: secrets filtering bypass via YAML aliases - CRIT-002: command injection in git operations - CRIT-003: unbounded memory in embedding batch - CRIT-004: race condition in index initialization High priority fixes (23): - Security: input validation, error message sanitization - Performance: connection pooling, lazy loading, caching - Architecture: index module split into package - Test coverage: exception paths, edge cases Medium priority fixes (8 code changes): - Thread-safe user index initialization - Memory exhaustion prevention with file limits - Magic numbers extracted to constants - Type annotation accuracy improvements Remaining medium/low findings documented as BY DESIGN
New test files: - test_batcher.py: batch processing edge cases - test_llm_secrets_filtering.py: secrets in LLM responses - test_providers.py: LLM provider implementations - tests/hooks/: hook handler test suite Updated tests: - test_capture.py: lock retry constants - test_embedding.py: batch size limits, memory safety - test_index.py: refactored module imports - test_implicit_capture_agent.py: expanded coverage - test_otlp_exporter.py: OTLP configuration tests Coverage: 2860 tests, 85.51% coverage
Updated CODE_REVIEW.md: - Marked all findings with resolution status - Added verification checkmarks for completed items Updated REMEDIATION_TASKS.md: - All 97 findings tracked with completion status - Summary tables for each priority level - Code fix vs BY DESIGN classification
- marketplace.json: version bump - plugin.json: cleanup unused fields - hooks.json: adjust hook configurations - otel-collector-config.yaml: simplify OTLP settings
- Remove const_labels from Prometheus exporter configuration - Fixes "duplicate label names" error when resource attributes already contain service.name - Root cause: const_labels conflicted with resource processor attributes
- Add LogRecord dataclass for structured log entries - Add export_logs() method to OTLPExporter - Add export_logs_if_configured() convenience function - Integrate log export in stop_handler for session events - Logs include: session end summary, captured memories, uncaptured warnings - Data flow: Hook Events → LogRecord → OTLP → Loki → Grafana
- Add traces.json: Tempo traces dashboard with trace search, export rate - Add logs.json: Loki logs dashboard with log stream, volume by level, filtered views for errors and memory operations - Both dashboards auto-provisioned in "Git Notes Memory" folder
- Fix docker-compose.yml: document correct env vars (MEMORY_PLUGIN_OTLP_*) - Add SSRF override documentation (MEMORY_PLUGIN_OTLP_ALLOW_INTERNAL) - Add --export flag to metrics.py for manual OTLP export - Update metrics.md command help with export option - Update CLAUDE.md observability section
Add immediate metrics export after each operation instead of only at session end. This enables real-time monitoring in Grafana dashboards. Changes: - Add flush_metrics_to_otlp() helper in hook_utils.py - Export metrics after each capture in capture.py - Flush metrics before exit in all hook handlers - Add monitoring optional dependencies to pyproject.toml - Fix OTEL collector config for Loki integration 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace push_notes_to_remote() with sync_notes_with_remote(push=True) in the Stop hook to prevent race conditions in multi-worktree environments. The sync function implements fetch→merge→push workflow with cat_sort_uniq merge strategy, preventing non-fast-forward push failures when concurrent Claude sessions operate on the same repository. Fixes #28 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR implements a critical fix for race conditions in multi-worktree environments (#28) along with several architectural improvements and new features. The main changes include:
- Core Fix: Replaced simple
push_notes_to_remote()withsync_notes_with_remote(push=True)in the Stop hook, implementing a fetch→merge→push workflow using thecat_sort_uniqmerge strategy to prevent non-fast-forward push failures when multiple Claude sessions operate concurrently - Architecture Refactoring: Decomposed God Objects (IndexService, observability module) into smaller, focused components following Single Responsibility Principle
- New Features: Added user-domain (global) memories, PII scrubbing, SSRF protection for OTLP endpoints, and real-time metrics flushing
Reviewed changes
Copilot reviewed 97 out of 147 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
stop_handler.py |
Replaced push-only with sync workflow to fix race conditions |
index/service.py |
Refactored into composed components (SchemaManager, SearchEngine) |
observability/__init__.py |
Dictionary-based lazy imports for better maintainability |
config.py |
Added Domain enum and user-memories configuration |
git_ops.py |
Added GitOpsFactory and user repo initialization |
capture.py |
Added domain parameter and stale lock detection |
| Multiple test files | Type annotation modernization (Optional → Union syntax) |
| Documentation files | New subconsciousness specification documents |
| if _is_private_ip(parsed.hostname): | ||
| allow_internal = os.environ.get( | ||
| "MEMORY_PLUGIN_OTLP_ALLOW_INTERNAL", "" | ||
| ).lower() in ("true", "1", "yes") |
Copilot
AI
Dec 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The SSRF protection correctly blocks internal IPs by default, but the allow list check should use a more restrictive set of truthy values. Including "yes" could lead to confusion since "yes1" or "yesplease" would be rejected. Consider using only ("true", "1") for the truthy check.
| ).lower() in ("true", "1", "yes") | |
| ).lower() in ("true", "1") |
| def scrub_pii(text: str) -> str: | ||
| """Scrub personally identifiable information from text. | ||
| Removes common PII patterns including: | ||
| - Email addresses | ||
| - Phone numbers (US format) | ||
| - Social Security Numbers | ||
| - Credit card numbers | ||
| - API keys and tokens | ||
| - AWS access keys | ||
| - Passwords and secrets in key=value format |
Copilot
AI
Dec 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The docstring states "Phone numbers (US format)" but this function is used in an international codebase. Consider documenting that only US phone formats are covered, or noting this as a limitation that may need extension for international deployments.
| logger.warning( | ||
| "Lock held by dead process (pid=%d), will acquire on next attempt", | ||
| holder_pid, | ||
| ) |
Copilot
AI
Dec 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The stale lock detection logs a warning but continues retrying until timeout. Consider adding a metric to track stale lock occurrences for monitoring purposes, as frequent stale locks could indicate improper process termination or system issues.
| ) | |
| ) | |
| try: | |
| metrics = get_metrics() | |
| metrics.increment("git_notes.capture.lock.stale") | |
| except Exception: | |
| logger.debug( | |
| "Failed to record stale lock metric", exc_info=True | |
| ) |
| """ | ||
| CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5( | ||
| id UNINDEXED, | ||
| summary, | ||
| content, | ||
| content='memories', | ||
| content_rowid='rowid' | ||
| ) | ||
| """, |
Copilot
AI
Dec 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The FTS5 virtual table uses external content mode (content='memories') which is efficient but requires manual trigger maintenance. The triggers are correctly defined (lines 103-124), but consider documenting this coupling in a comment within the migration to help future maintainers understand that deleting these triggers would break FTS5 sync.
| self._circuit_breaker.record_failure() | ||
| raise EmbeddingError( | ||
| f"Embedding timed out after {EMBED_TIMEOUT_SECONDS}s", | ||
| "The model may be overloaded or GPU memory exhausted. Restart and retry.", |
Copilot
AI
Dec 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message "Restart and retry" is vague - restart what? The process, the GPU, the system? Consider being more specific, such as "Restart the embedding service or reduce concurrent embedding requests."
| "The model may be overloaded or GPU memory exhausted. Restart and retry.", | |
| ( | |
| "The model may be overloaded or GPU memory exhausted. " | |
| "Restart the embedding service and/or reduce concurrent " | |
| "embedding requests, then retry the operation." | |
| ), |
| # Also verify parent directory is not a symlink | ||
| if user_path.parent.is_symlink(): | ||
| raise StorageError( | ||
| "User memories parent directory is a symlink", | ||
| f"Remove symlink at {user_path.parent} and retry", | ||
| ) |
Copilot
AI
Dec 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The symlink check at SEC-HIGH-001 correctly prevents symlink attacks on the target path. However, consider also checking if any intermediate directories in the path are symlinks (similar to the parent check on line 1420), as an attacker could create a symlink in the middle of the path hierarchy.
| # Also verify parent directory is not a symlink | |
| if user_path.parent.is_symlink(): | |
| raise StorageError( | |
| "User memories parent directory is a symlink", | |
| f"Remove symlink at {user_path.parent} and retry", | |
| ) | |
| # Also verify that no intermediate directory in the hierarchy is a symlink | |
| for ancestor in user_path.parents: | |
| if ancestor.is_symlink(): | |
| raise StorageError( | |
| "User memories path contains a symlinked directory", | |
| f"Remove symlink at {ancestor} and retry", | |
| ) |
| summary_key = mem.summary.lower().strip() | ||
| if summary_key not in seen_summaries: | ||
| seen_summaries.add(summary_key) | ||
| deduplicated.append((mem, dist)) |
Copilot
AI
Dec 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The deduplication logic uses exact summary matching after lowercasing. This could miss near-duplicates like "Fix bug in parser" vs "Fixed bug in parser." Consider using a similarity threshold or normalization (removing punctuation, stemming) for more robust deduplication.
Summary
push_notes_to_remote()withsync_notes_with_remote(push=True)in Stop hookcat_sort_uniqmerge strategyRelated Issue
Fixes #28
Changes
Core Fix (
stop_handler.py:473-490)The Stop hook now uses
sync_notes_with_remote(push=True)which:This prevents non-fast-forward push failures in multi-worktree environments.
Supporting Changes (separate commit)
Test Plan
Spec Documents
See
docs/spec/active/2025-12-26-multi-worktree-sync-fix/for:🤖 Generated with Claude Code