feat: Permissions Extension (MDD) — Shared Package and Standalone Hooks - #13
Merged
Conversation
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Implements the Permissions Extension (MDD) by extracting command classification into a shared pkg/classifier package, persisting approval rules + analytics in SQLite/Ent, and adding a standalone ssq-hooks CLI plus hook/wrapper scripts for non-Claude agents.
Changes:
- Added new Ent schemas/tables for
approval_rulesandclassification_analytics, plus enabled Ent SQL upsert generation. - Refactored server services to use
pkg/classifierand moved rules/analytics persistence from files → SQLite viasession.Storage. - Introduced
ssq-hooks(check/serve/proxy/install) and added scripts for Gemini + Open Code interception.
Reviewed changes
Copilot reviewed 78 out of 108 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| session/ent/tag_create.go | Adds Ent upsert support for Tag create/bulk operations. |
| session/ent/tag/where.go | Import reordering for generated predicate usage. |
| session/ent/tag.go | Import reordering for Tag model. |
| session/ent/session_update.go | Import reordering for Session update builder. |
| session/ent/session_query.go | Import reordering for Session query builder. |
| session/ent/session_delete.go | Import reordering for Session delete builder. |
| session/ent/session/where.go | Import reordering for Session predicates. |
| session/ent/session.go | Import reordering for Session model. |
| session/ent/schema/classificationanalytics.go | New Ent schema for classification analytics records. |
| session/ent/schema/approvalrule.go | New Ent schema for approval rules persisted in DB. |
| session/ent/runtime.go | Wires runtime validators/defaults for new ApprovalRule/ClassificationAnalytics schemas. |
| session/ent/predicate/predicate.go | Adds predicate types for new schemas. |
| session/ent/migrate/schema.go | Adds migrate table definitions for approval_rules and classification_analytics. |
| session/ent/hook/hook.go | Adds hook adapters for new schemas. |
| session/ent/generate.go | Enables Ent sql/upsert feature for codegen. |
| session/ent/enttest/enttest.go | Import reordering; keeps migrate wiring. |
| session/ent/ent.go | Registers new tables for column validation. |
| session/ent/diffstats_update.go | Import reordering for DiffStats update builder. |
| session/ent/diffstats_query.go | Import reordering for DiffStats query builder. |
| session/ent/diffstats_delete.go | Import reordering for DiffStats delete builder. |
| session/ent/diffstats_create.go | Adds Ent upsert support for DiffStats create/bulk operations. |
| session/ent/diffstats/where.go | Import reordering for generated predicate usage. |
| session/ent/diffstats.go | Import reordering for DiffStats model. |
| session/ent/client.go | Adds ApprovalRule/ClassificationAnalytics clients + Tx wiring. |
| session/ent/claudesession_update.go | Import reordering for ClaudeSession update builder. |
| session/ent/claudesession_query.go | Import reordering for ClaudeSession query builder. |
| session/ent/claudesession_delete.go | Import reordering for ClaudeSession delete builder. |
| session/ent/claudesession/where.go | Import reordering for ClaudeSession predicates. |
| session/ent/claudesession.go | Import reordering for ClaudeSession model. |
| session/ent/claudemetadata_update.go | Import reordering for ClaudeMetadata update builder. |
| session/ent/claudemetadata_query.go | Import reordering for ClaudeMetadata query builder. |
| session/ent/claudemetadata_delete.go | Import reordering for ClaudeMetadata delete builder. |
| session/ent/claudemetadata_create.go | Adds Ent upsert support for ClaudeMetadata create/bulk operations. |
| session/ent/claudemetadata/where.go | Import reordering for ClaudeMetadata predicates. |
| session/ent/claudemetadata.go | Import reordering for ClaudeMetadata model. |
| session/ent/classificationanalytics_query.go | New generated query builder for ClassificationAnalytics. |
| session/ent/classificationanalytics_delete.go | New generated delete builder for ClassificationAnalytics. |
| session/ent/classificationanalytics/classificationanalytics.go | New schema constants/ordering for ClassificationAnalytics. |
| session/ent/classificationanalytics.go | New model + scan/assign logic for ClassificationAnalytics. |
| session/ent/approvalrule_query.go | New generated query builder for ApprovalRule. |
| session/ent/approvalrule_delete.go | New generated delete builder for ApprovalRule. |
| session/ent/approvalrule/approvalrule.go | New schema constants/ordering for ApprovalRule. |
| session/ent/approvalrule.go | New model + scan/assign logic for ApprovalRule. |
| server/services/session_service.go | Switches to pkg/classifier and moves rules/analytics stores to DB-backed implementations. |
| server/services/rules_store.go | Migrates rules storage from JSON file → SQLite; adds export-to-config-file for hooks. |
| server/services/rules_service.go | Updates service types to use pkg/classifier rules + seed rules. |
| server/services/claude_settings_parser.go | Updates Claude settings rule conversion to pkg/classifier types. |
| server/services/approval_store.go | Removes duplicated PermissionRequestPayload (now in shared pkg). |
| server/services/approval_handler.go | Uses shared classifier interfaces/types and payload DTO. |
| server/services/analytics_store.go | Migrates analytics from JSONL → SQLite via session.Storage. |
| scripts/hooks/open-code-proxy.sh | Adds wrapper script that runs Open Code through ssq-hooks proxy. |
| scripts/hooks/install-gemini-hook.sh | Adds instructions/script to set Gemini BeforeTool hook to ssq-hooks. |
| project_plans/permissions-extension-mdd/research/stack.md | Adds research notes on integration points + binary delivery. |
| project_plans/permissions-extension-mdd/research/research_plan.md | Adds research plan doc. |
| project_plans/permissions-extension-mdd/research/pitfalls.md | Adds pitfalls/risk analysis doc. |
| project_plans/permissions-extension-mdd/research/features.md | Adds comparable tools/feature survey doc. |
| project_plans/permissions-extension-mdd/research/architecture.md | Adds architecture research doc. |
| project_plans/permissions-extension-mdd/requirements.md | Adds requirements doc for the project plan. |
| project_plans/permissions-extension-mdd/decisions/ADR-002-hook-interception-strategy-wrapper.md | Adds ADR for wrapper-based interception strategy. |
| project_plans/permissions-extension-mdd/decisions/ADR-001-package-structure-reusable-classifier.md | Adds ADR for extracting reusable classifier package. |
| project_plans/permissions-extension-mdd/architecture.md | Adds additional architecture doc. |
| pkg/classifier/command_parser.go | Moves parser into shared pkg; adds redirects extraction + deep audit utilities. |
| pkg/classifier/classifier_test.go | Updates tests package name to classifier. |
| pkg/classifier/classifier.go | Moves classifier into shared pkg; adds payload DTO + redirection criteria + deep audit enforcement. |
| docs/tasks/TODO.md | Updates tasks index to reflect completed plans. |
| docs/bugs/fixed/BUG-009-session-package-test-failures.md | Marks BUG-009 fixed. |
| docs/archive/tasks/completed/permissions-extension-mdd.md | Adds archived completed implementation plan doc. |
| cmd/ssq-hooks/main.go | Adds standalone ssq-hooks CLI with check/serve/proxy/install. |
| TODO.md | Updates top-level TODO with “Permissions Extension (MDD)” completion notes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
tstapler
force-pushed
the
stapler-squad-permissions-extension
branch
from
April 11, 2026 04:17
1515e4c to
f3820a0
Compare
tstapler
pushed a commit
that referenced
this pull request
Apr 11, 2026
… management Squash of spike/rtk-state-management. See PR #13 for full description.
tstapler
force-pushed
the
stapler-squad-permissions-extension
branch
from
April 11, 2026 05:34
6c6c77c to
a89014e
Compare
tstapler
pushed a commit
that referenced
this pull request
Apr 11, 2026
… management Squash of spike/rtk-state-management. See PR #13 for full description.
…r and ssq-hooks CLI
After rebasing onto main, several build errors needed fixing: - Add broadcastQuestionNotification, writeDeferDecision, normalizeSessionID, and truncateString methods from main to approval_handler.go - Qualify PermissionRequestPayload with classifier. prefix - Import pkg/classifier in path_matcher.go for ClassificationContext type - Use concStorage instead of storage interface for RulesStore/AnalyticsStore Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
tstapler
force-pushed
the
stapler-squad-permissions-extension
branch
from
April 12, 2026 02:47
a89014e to
78798ac
Compare
- Add classifier.ClassificationContext import to path_matcher_test.go (type moved to pkg/classifier/ during extraction) - Convert string session types to SessionType enum in OmnibarContext to fix TypeScript type error Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Contributor
Frontend Terminal Throughput |
- ssq-hooks: handle storage initialization error instead of ignoring - ssq-hooks: log and skip rules with invalid regex patterns instead of silently creating rules with nil patterns - open-code-proxy.sh: replace eval with exec to avoid shell injection - install-gemini-hook.sh: fix hook command to use stdin pipe (matches ssq-hooks check's actual interface) - rules_store: export RuleSpec structs instead of compiled Rules (regexp fields don't round-trip through JSON) - analytics_store: add TODO for timestamp-filtered DB query - ent/hook: fix grammar "expect" → "expected" in error messages Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Contributor
Go Benchmarks (Tier 1) |
- Qualify ClassificationContext, NewRuleBasedClassifier, AutoDeny, PermissionRequestPayload with classifier. prefix in path_matcher_test - Fix connect.PeerFromContext/WithPeer (don't exist in connectrpc): extract validateLocalhostAddr for testability, simplify test - Run gofmt on unformatted files Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Contributor
E2E RPC Latency |
Lint fixes: - Apply De Morgan's law in ssq-hooks, classifier, command_parser - Remove unnecessary fmt.Sprintf wrapper in command_parser - Remove empty branch in analytics_store - Use fmt.Fprintf instead of WriteString(Sprintf) in terminal_state - Remove unused normalizeSessionID in approval_handler Classifier fix: - Narrow rm-rf seed rule regex to match only exact /, ~, $HOME (not paths starting with /) by requiring trailing whitespace or EOL - Update ExpandedHome test to reflect that the seed regex can't match runtime-expanded home paths (needs path-classification rule) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add audit-rm-rf-critical-path security finding in AuditCommand that detects rm with recursive+force flags targeting root (/) or the user's home directory, even when the path is already expanded (e.g. /home/user instead of ~ or $HOME). The seed regex rule handles literal /, ~, $HOME patterns. The new AST audit handles the runtime-expanded case by resolving ~ and $HOME in arguments and comparing against the actual home directory. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
tstapler
commented
Apr 12, 2026
Thread ClassificationContext.Cwd through AuditCommand so relative paths like "." and ".." are resolved against the Claude session's working directory before checking against critical paths. Examples now caught: - rm -rf . (from /) → resolves to /, denied - rm -rf .. (from ~/projects) → resolves to ~, denied - rm -rf . (from /tmp/proj) → resolves to /tmp/proj, allowed Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
tstapler
added a commit
that referenced
this pull request
Apr 16, 2026
* chore: remove CLA and rename Claude Squad to Stapler Squad
- Delete CLA.md and the CLA assistant GitHub Actions workflow
- Rename "Claude Squad" to "Stapler Squad" in TLS cert fields,
git commit author name, and WebAuthn display name
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: Enhance session management and CLI
This commit introduces several improvements to session management and the CLI:
- **Automatic Session Restart:** Sessions now automatically restart with the new program when the 'program' field is updated in a running session via the API. This ensures changes take effect immediately without manual intervention.
- **Session History Preservation:** Shell history for each session is now isolated and preserved in a unique file within the session's working directory. This allows for persistent command history across program changes within the same session.
- **New CLI Command for Session Info:** Added a new 'get-session <name>' CLI command to retrieve detailed information about a session in JSON format, facilitating easier inspection and integration with other tools.
The web UI for inline program editing will require frontend changes to utilize the updated backend API for automatic restarts.
* feat: add push notifications and PWA support for mobile experience
This commit adds Progressive Web App capabilities and push notification
support to Stapler Squad, enabling better mobile experience and
real-time notifications for session events.
## Changes Made
### Go Backend
- Added PushService with VAPID key management
- Added HTTP endpoints for push subscription management
- Added push notification event subscriber
- Integrated push service into dependency injection
### Web App
- Added PWA manifest with app metadata
- Added service worker for push notifications
- Added React hook for push notification management
- Added Apple Web App support for iOS
## Features
### Push Notifications
- Session completed notifications
- Approval required notifications
- Deep linking to specific sessions
### Mobile Experience
- Installable via Add to Home Screen
- Standalone app mode
- Theme color and viewport fit
- Safe area handling
## API Endpoints
- GET /api/push/vapid-key - Get VAPID public key
- POST /api/push/subscribe - Subscribe to push
- POST /api/push/unsubscribe - Unsubscribe from push
## Testing
- All server tests pass
- Web app builds successfully
- Go application compiles
Resolves: #[issue-number]
* feat(session): work-in-progress session improvements, virtual keyboard, and detection enhancements
* fix(test): resolve false positive in session recovery test and add web grouping unit tests
* docs: Archive completed plans session-restart and session-rename-restart
These features have been fully implemented in the codebase:
- Session restart and rename functionality now working
* docs: Remove obsolete task plans and archive completed plans
Removed obsolete plans (no longer relevant):
- circuit-breaker-executor.md
- claude-code-hook-approval.md
- conductor-feature-parity.md
- dependency-initialization-hardening.md
- session-service-decomposition.md
Archived completed plans:
- session-restart-functionality.md (moved to completed/)
- session-rename-restart.md (moved to completed/)
* feat: implement terminal tab stops
Implemented support for tab stops in the terminal state handler,
allowing custom tab stop configurations as required by UI components
or complex shell applications.
- Added `TabStops` map to `TerminalState` struct
- Configured default tab stops at every 8th column
- Updated the `\t` handler to jump to the next valid tab stop
- Implemented `ESC H` (HTS) to set a tab stop at the current column
- Implemented `CSI g` (TBC) to clear tab stops
- Ensured `TabStops` state propagates during `Clone` and `Resize`
- Added comprehensive unit tests for tab stop behavior
Co-authored-by: tstapler <3860386+tstapler@users.noreply.github.com>
* fix: properly manage tab stops and CI formats
- Added proper tests and functionality for `ESC H`, `\t`, and `CSI g` related to tab stop clearing and setting
- Fixed a dangling format issue in `session/terminal_state.go`
- Resolved a CI regression by ensuring `go.mod` retains the original Go 1.25.0 spec rather than degrading, and `actions/setup-go` falls back to its original version tag to avoid unfound version action failures.
Co-authored-by: tstapler <3860386+tstapler@users.noreply.github.com>
* feat: implement terminal tab stops
Implemented support for tab stops in the terminal state handler,
allowing custom tab stop configurations as required by UI components
or complex shell applications.
- Added `TabStops` map to `TerminalState` struct
- Configured default tab stops at every 8th column
- Updated the `\t` handler to jump to the next valid tab stop
- Implemented `ESC H` (HTS) to set a tab stop at the current column
- Implemented `CSI g` (TBC) to clear tab stops
- Ensured `TabStops` state propagates during `Clone` and `Resize`
- Added comprehensive unit tests for tab stop behavior
Co-authored-by: tstapler <3860386+tstapler@users.noreply.github.com>
* security: fix IP spoofing in localhost validation
Replaced forgeable HTTP header checks (X-Real-IP, X-Forwarded-For) with
actual network connection peer address validation using req.Peer() and
connect.PeerFromContext(). Added unit tests to verify the fix and
ensure spoofed headers are ignored.
Co-authored-by: tstapler <3860386+tstapler@users.noreply.github.com>
* security: fix IP spoofing in localhost validation
Replaced forgeable HTTP header checks (X-Real-IP, X-Forwarded-For) with
actual network connection peer address validation using req.Peer() and
connect.PeerFromContext(). Added unit tests to verify the fix and
ensure spoofed headers are ignored. Removed unused strings import.
Co-authored-by: tstapler <3860386+tstapler@users.noreply.github.com>
* build: unify CI with makefile and suppress sqlite3 warnings
- Update GitHub workflows to use make commands and Go 1.25.0
- Include linting in make build target
- Export CGO_CFLAGS to suppress discarded-qualifiers warnings from sqlite3
- Update documentation regarding new build requirements
* fix: address project-wide linting and code quality issues
- Fix errcheck, unused, ineffassign, staticcheck, and nilnil violations
- Remove unused code and imports (e.g., realCommandExecutor, strconv)
- Replace forbidden fmt.Printf with structured logging
- Standardize error handling with sentinel errors where appropriate
- Apply project-wide formatting using gofmt
* fix(detection): add per-program pattern routing for Gemini notifications
Refactor StatusDetector to use per-program compiled pattern sets
(claude, gemini, aider, opencode) instead of a single merged set.
The core bug fix is in review_queue_poller.go: DetectWithContext() is
now DetectWithContextForProgram(..., inst.Program), so Gemini sessions
use Gemini-specific patterns (e.g. [INSERT] for idle, Thinking... for
active) rather than falling through to the merged fallback.
Also adds a snapshot test framework with 14 real terminal output
fixtures covering all programs × states for regression coverage.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(detection): add missing OpenCode Processing/InputRequired/Active patterns
opencodePatterns() was missing patterns that main added in
detector_opencode_test.go:
- Processing: opencode_thinking, opencode_reading, opencode_writing
- InputRequired: opencode_numbered_options, opencode_permission (Allow once/always/Reject)
- Active: opencode_esc_interrupt
These are needed so Detect() (the "" fallback set) and
DetectForProgram(..., "opencode") both pass the OpenCode tests
added to main.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(workspace): update TestRegistryUnregister to expect ErrNotFound
TestRegistryUnregister was incorrectly failing because WorkspaceRegistry.Get() returns ErrNotFound when a workspace is not found, but the test expected a nil error.
* ci: fix 'make lint' failure by adding Node.js and Buf setup steps
The Lint workflow was failing because 'make lint' has a dependency chain that eventually requires 'buf' for proto generation and potentially node for web assets. Added missing environment setup steps to match the Build workflow.
* security: robust localhost validation and CI fixes
- Update validateLocalhostOrigin to use connect.Peer struct and context fallback
- Remove unused strings import in notification_service.go
- Add .gitkeep to dist directories to satisfy go:embed requirements in CI
- Improve security test logic using connect.WithPeer
Co-authored-by: tstapler <3860386+tstapler@users.noreply.github.com>
* ci: fix golangci-lint path issue in unified lint command
- Update Makefile to install golangci-lint if missing and use absolute path from GOPATH/bin
- Restore 'make lint' in lint.yml for a unified experience
* ci: trigger CI on Makefile changes
- Add Makefile to paths filter in Lint and Build workflows
- This ensures build system fixes actually get tested in CI
* feat(session,ui): add automatic rate limit detection and recovery
Implements automatic detection of rate limit dialogs in LLM programs with scheduled recovery. Includes:
- Detection patterns for Anthropic, OpenAI, Google, and Aider providers
- Timestamp parsing from dialog messages (retry after X, access resets at)
- Scheduled recovery with configurable buffer time
- Recovery input automation (sends input to resume session)
- Integration with ClaudeController for lifecycle management
- Per-session enable/disable via Instance methods
- Rate limit state exposed via Instance.GetRateLimitState()
- Proto definition for RateLimitState enum
API additions:
- Instance.GetRateLimitState() -> int
- Instance.SetRateLimitEnabled(bool)
- Instance.IsRateLimitEnabled() -> bool
- ClaudeController.GetRateLimitState()
* feat(web-ui): display rate limit status in session cards
Shows rate limit status on session cards with color-coded badges:
- Yellow 'Rate Limited' when waiting for reset
- Purple 'Recovering...' during recovery
- Green 'Recovered' after successful recovery
- Red 'Recovery Failed' on failure
* docs: add rate limit feature planning and testing documentation
- Feature plan with 6 stories covering detection, parsing, recovery, events, config, testing
- Test plan with 112 test cases organized by component
- UX improvements document with prioritized tasks
- Project research documents (stack, features, architecture, pitfalls)
- Requirements document (problem statement, scope, constraints)
* ci: fix Lint workflow by building web UI and using golangci-lint action
The Lint workflow was failing because: 1. golangci-lint was not installed. 2. type checking failed because server/web/dist did not exist (needed for go:embed). Added 'make web-build' and switched to the official action.
* fix(ci): ensure proto generation runs before web UI build
The web UI build depends on generated proto files. Add proto-gen as a
prerequisite to web-app/out to ensure proto generation runs first in CI.
* refactor(terminal): move Unix-specific signals and sizing to platform files
This enables Windows compilation by isolating SIGWINCH and IOCTL usage. Added dummy implementations for Windows.
* build: update dependencies and lint target
Preparing for merge with upstream by committing local build changes.
* build: improve Makefile, tool versions, and hostname detection for Linux
* feat: display detected LAN hostnames on settings page
* fix(auth): support multiple domains for passkey auth
* fix(auth): support multiple origins for CORS
* fix(auth): support multiple domains for passkey auth and CORS
* feat(frontend): adopt Redux Toolkit + protobuf-es v2 for shared state management
Squash of spike/rtk-state-management. See PR #13 for full description.
* fix: remove tsc from node-tools rule to allow ForbiddenFlags matching
seed-allow-bash-node-tools was matching tsc unconditionally before
seed-allow-bash-tsc could run, causing ForbiddenFlags to never fire.
Removing tsc from node-tools lets the dedicated tsc rule correctly
escalate output-writing invocations (--outDir, --outFile, --declaration).
* build: fix asdf nodejs plugin name and add Makefile dependency caching
- Rename node→nodejs in .tool-versions to match the asdf plugin name
- Add .asdf-install.stamp file target so asdf install only runs when
.tool-versions changes, not on every make invocation
- Add web-app/node_modules/.package-lock.json file target to cache npm
installs triggered by package.json/package-lock.json changes
- Wire proto-gen to depend on the npm target and check protoc-gen-es
binary mtime vs stamp to detect plugin version changes
- Add `make qr` target that skips the full web rebuild when the binary
already exists
- Fix echo path: web/src/gen/ → web-app/src/gen/
* fix(notifications): deduplicate approval_needed records and prevent double-publish
BUG-001: Skip EventBus publish in ReactiveQueueManager.OnItemAdded when
item.Reason==ReasonApprovalPending. ApprovalHandler.broadcastApprovalNotification
is the authoritative source; the second publish created a duplicate card in the
notification panel.
BUG-003: Enable (sessionID, notificationType) deduplication for APPROVAL_NEEDED
records. When collapsing into an existing unread record the record ID is rotated
to the incoming approval UUID so SetMetadata outcome-stamping continues to work.
Three sequential approvals now collapse to a single record with OccurrenceCount=3
and ID=latest approval UUID.
* fix(approval): normalize tmux-prefixed session IDs in permission hook handler
BUG-002: ApprovalHandler now calls normalizeSessionID() when the X-CS-Session-ID
header is present. If the header value is a tmux-prefixed session name (e.g.
"staplersquad_my-session"), the method strips the prefix and returns the canonical
instance title. This prevents the same session appearing twice in the notification
panel under different IDs when the global hook uses the tmux name while all other
notification paths use the canonical title.
* fix(secret-scanner): exclude shell variable references from plaintext secret patterns
Patterns for inline secret env vars, database credential env vars, and CLI flags
previously matched shell variable references such as -Pflyway.password="\$RDS_TOKEN",
causing false positives that would auto-deny legitimate commands.
Updated all three patterns to require the value portion to be a literal string
(not starting with \$ or referencing a shell variable). Added
TestScanForSecrets_NoFalsePositives covering this and related edge cases.
* fix(classifier): escalate AskUserQuestion to review queue
Remove askuserquestion from builtinAgentTools so the tool is no longer
auto-allowed. It now escalates to the manual review queue, giving users
visibility into and control over questions Claude asks during a session.
Also add test coverage for OPA (fmt/test/check auto-allow; eval/run escalate)
and TSC (--noEmit auto-allow; --outDir/--outFile/--declaration escalate).
* feat(review-queue): add keyboard shortcuts, auto-advance toggle, and focus trap
- Global keyboard shortcuts: ? toggles help overlay, Escape closes modal or help
- Auto-advance preference (default on) persisted to localStorage; checkbox in toolbar
- Focus trap inside session-detail modal using useFocusTrap hook
- ARIA attributes on modal dialogs (role=dialog, aria-modal, aria-label/labelledby)
- Keyboard hints component rendered in help overlay
- KeyboardHints, useFocusTrap, useKeyboard wired into review-queue page
* docs(bugs): add review-queue-gaps tracking document
Documents BUG-001 through GAP-004 identified during architecture review:
- BUG-001/003 (fixed): duplicate notifications and APPROVAL_NEEDED dedup
- BUG-002 (fixed): session ID inconsistency in approval notifications
- GAP-001: approval timeout UX degrades silently
- GAP-002: no risk-weighted sorting within approval_pending tier
- GAP-003: websocket reconnect falls back to 30s poll
- GAP-004: multi-approval session shows only first approval
* test(classifier): update AskUserQuestion expected category to ToolCategoryBuiltin
AskUserQuestion was intentionally removed from builtinAgentTools so it
escalates to the review queue. Update the test to reflect the new expected
category instead of ToolCategoryBuiltinAgent.
* perf(session): fix tight loop and GC pressure in waitForCommandOrDrain
Two bugs caused ~294% CPU usage when a CommandExecutor's response channel
was closed (e.g. tmux session stopped):
1. time.After(1s) inside the for loop allocated a new timer+channel on
every iteration, leaking ~18 million timers/sec under load.
2. When responseCh was closed, the select case returned immediately with
ok=false, but the caller (executionLoop) ignored the return and kept
calling waitForCommandOrDrain in a tight infinite spin.
Fix both:
- Move time.NewTimer outside the loop with defer Stop()
- Return bool from waitForCommandOrDrain (false = channel closed)
- Exit executionLoop when false is returned
CPU usage dropped from ~294% to ~2%.
* feat(ui): show relative timestamps on notification toasts and prune stale entries
- Replace absolute locale time with a live relative timestamp (e.g. "just
now", "30s ago", "2 mins ago") that ticks every second; the full time
is preserved as a hover tooltip
- Purge notifications older than 5 minutes from state every minute so
stale toasts do not accumulate in the panel indefinitely
* docs: Add feature plan for performance-benchmarking
Implementation plan covering 6 stories across Go CI benchmark workflow,
ConnectRPC handler benchmarks, terminal pipeline benchmarks, frontend
bundle/throughput gates, profiling runbook, and E2E latency measurement.
Includes 3 ADRs:
- ADR-001: CI benchmark storage (benchmarks branch + dual thresholds)
- ADR-002: Tiered benchmark organization (Tier 1-3 execution strategy)
- ADR-003: Frontend measurement strategy (size-limit + Playwright throughput)
* feat(benchmarks): implement comprehensive performance benchmarking system
Adds automated regression detection for Go, frontend, and E2E layers:
Go benchmarks:
- CI workflow (.github/workflows/benchmark.yml) with 3-tier execution
- Tier 1: critical-path benchmarks on every PR (alert 120%, fail 150%)
- Tier 2: full suite on push to main
- Tracks baselines on dedicated 'benchmarks' branch via github-action-benchmark
- server/terminal/delta_bench_test.go: 5 realistic ANSI benchmarks
(100KB payloads, rapid sequential updates, progress bars, 200x50 screens)
- session/scrollback/buffer_bench_test.go: 4 concurrency benchmarks
(GOMAXPROCS concurrent read/write, burst eviction, large buffer GetLastN)
- server/services/session_service_bench_test.go: 3 ConnectRPC benchmarks
(ListSessions empty, 50-session load, GetSession single retrieval)
Frontend benchmarks:
- web-app/.size-limit.json: bundle size regression gate (5MB total, 650KB React)
- web-app/lighthouserc.json: advisory Lighthouse CI (warn-only, never blocks)
- web-app/tests/e2e/benchmarks/terminal-throughput.spec.ts: xterm.js throughput
(10 runs, 2 warmup discards, single evaluate() timing to eliminate IPC overhead)
- web-app/tests/e2e/benchmarks/rpc-latency.spec.ts: ListSessions TTFB + total latency
- web-app/tests/e2e/benchmarks/output-benchmark-results.ts: shared result formatter
Makefile / tooling:
- benchmark-baseline, benchmark-compare (benchstat A/B), benchmark-tier1
- profile-goroutines, profile-block, profile-mutex, profile-trace targets
- benchstat added to install-tools
- bench-*.txt added to .gitignore
Documentation:
- docs/PROFILING.md: 506-line runbook covering CPU, heap, goroutine, block,
mutex, trace, benchmark profiling, and Speedscope (with copy-paste commands)
Verified: all Go benchmarks compile and run (5120 MB/s delta, 1.58ms ListSessions)
* fix(benchmarks): include new delta benchmarks in Tier 1 and add backend readiness probe
- Broaden Tier 1 regex from `BenchmarkDeltaGeneration` to `BenchmarkDeltaGenerat`
so it matches both the existing BenchmarkDeltaGeneration (delta_test.go) and
all new BenchmarkDeltaGenerator_* benchmarks (delta_bench_test.go)
- Add `npx wait-on http://localhost:8543 --timeout 30000` after starting the
backend in the e2e-latency CI job, preventing a race where Playwright fires
before the server is ready to accept connections
* fix(benchmarks): address code quality, security, and ops review findings
Go test code:
- DRY: expose repo on benchServiceFixture; eliminate duplicated setup in
ListSessions_50Sessions and GetSession benchmarks
- Dead code: remove unused runtime.MemStats declaration and ReadMemStats call
- Magic number: replace literal 55 with named constant warmupIters in
delta_bench_test.go with explaining comment (fullSyncInterval+5)
TypeScript tests:
- rpc-latency: fix misleading comment about page.evaluate IPC overhead
- terminal-throughput: document that CI uses canvas renderer (no GPU) so
baselines are renderer-specific and not cross-comparable with local WebGL
CI workflow:
- Security: move permissions from global scope to per-job; frontend-bundle
and lighthouse no longer hold contents:write or pull-requests:write
- Security: SHA-pin benchmark-action/github-action-benchmark@v1 to
a60cea5bc7b49e15c1f58f411161f99e0df48372 (v1.22.0) to prevent tag drift
- Ops: run npx wait-on from web-app/ so it uses the locally installed package
instead of downloading from npm on each CI run
package.json:
- Add wait-on@^8.0.1 as devDependency for deterministic CI installs
* ci(bench): replace benchmark-action with file-based baselines in main
Drop the separate benchmarks branch (gh-pages style) in favour of
committing baseline files directly to main under benchmarks/:
benchmarks/go/tier1-baseline.txt
benchmarks/go/tier2-baseline.txt
benchmarks/frontend/throughput-baseline.json
benchmarks/e2e/latency-baseline.json
On every push to main each job copies its output to the corresponding
baseline file, commits, then does git pull --rebase && git push to
handle concurrent tier1/tier2 updates without races.
On pull requests each job compares the current run against the committed
baseline (benchstat for Go, inline Node.js diff for JSON metrics) and
creates or updates a marker-based PR comment so the comparison is always
visible in context.
Also updates docs/PROFILING.md recovery runbook to reference the new
file paths instead of the retired benchmarks branch.
* feat(approvals): auto-allow AskUserQuestion with informational toast
AskUserQuestion no longer blocks in the manual review queue. Instead:
- The hook handler intercepts it before the classifier, fires an
INPUT_REQUIRED notification (❓ toast with the question text), and
returns "allow" immediately so Claude asks in the terminal.
- classifier.go adds "askuserquestion" to builtinAgentTools so the
classifier also auto-allows it as a fallback.
- INPUT_REQUIRED notifications now map to the "question" UI type
(❓, no Approve/Deny buttons) instead of "approval_needed" in
useSessionNotifications.ts.
- writeDeferDecision() replaces the bare w.WriteHeader(200) in the
timeout path, documenting the "neither approve nor deny" behavior.
* fix: address all shipping gate blocking issues
- benchmark.yml: add regression exit-1 gate (>10% threshold) after benchstat comparison
- session/instance.go + storage.go: serialize AutonomousMode in ToInstanceData/FromInstanceData
- session/tmux/tmux.go: extract listSessionsRaw helper, eliminating duplicated cmdArgs/circuit-breaker fallback logic from DoesSessionExist and DoesSessionExistNoCache
- server/services/approval_handler.go: replace magic number 120 with maxNotificationMessageLen constant
- useSessionNotifications.ts: extend dedup bypass to include INPUT_REQUIRED (questions) alongside APPROVAL_NEEDED
* feat(tmux): add server resilience - auto-restart, keepalive, and smart circuit breaker
Layer 2 - Auto-restart: detect 'no server running' and restart tmux server on startup
and at runtime via DoesSessionExist/DoesSessionExistNoCache recovery paths.
Layer 3 - Keepalive session: create staplersquad_keepalive session on startup to
prevent the tmux server from exiting when all user sessions close.
Layer 1 (opt-in) - exit-empty flag: --tmux-keep-server CLI flag sets tmux's
server-level exit-empty option off for belt-and-suspenders protection.
Circuit breaker improvements:
- Add IsFailure classifier: tmux list-sessions exit-1 with empty output (no sessions)
no longer trips the breaker — only 'no server running' output does.
- Add Resettable interface: replaces *CircuitBreakerExecutor type assertion in restore path.
- Add Reset()/ResetAll(): called after server recovery to clear tripped breakers.
- Fix commandClass: skip -L/-S flag-value pairs so 'tmux -L sock list-sessions'
correctly produces class 'tmux-list-sessions' rather than 'tmux-<socket>'.
tmux.go additions:
- serverNotRunning(), checkServerNotRunning(), EnsureServerRunning(), SetExitEmpty(),
CreateKeepaliveSession(): package-level functions that use exec.Command directly
(bypassing per-session circuit breakers) since they operate on the server itself.
- tmuxCircuitBreakerConfig(): returns CB config with tmux-aware IsFailure classifier.
- recoverFromServerFailure(): called from DoesSessionExist on server-down detection.
- listSessionsRaw(): DRY helper eliminating duplicated cmdArgs/CB-fallback logic.
* test(tmux): add circuit breaker and server resilience tests
executor/circuit_breaker_test.go:
- TestCircuitBreakerExecutor_Reset_FromOpen/FromHalfOpen: verify Reset() restores CLOSED state
- TestCircuitBreakerRegistry_ResetAll: verify ResetAll() resets all registered executors
- TestCircuitBreakerRegistry_Unregister: verify executor removal removes its breakers from AllBreakers
- TestCircuitBreakerExecutor_IsFailure: verify custom classifier controls failure counting
- TestCommandClass: add -L/-S socket-flag skip cases
session/tmux/tmux_test.go:
- TestTmuxCircuitBreakerConfig: table-driven tests for all IsFailure classifier cases
(nil, no-server-running, error-connecting, empty-no-sessions, other commands)
- TestEnsureServerRunning_NoOp/StartsServer: verify no-op when running and start behavior
- TestCreateKeepaliveSession: verify idempotent keepalive session creation
- TestSetExitEmpty: verify exit-empty option toggle (skips in short mode, uses isolated socket)
session/tmux/circuit_breaker_test.go:
- TestTmuxCircuitBreakerConfig_NoSessionsNotFailure: comprehensive table-driven CB classifier test
- TestDoesSessionExist_CircuitBypassFallback: verify CB open returns bool without panic
- TestDoesSessionExistNoCache_CircuitBypassFallback: verify mock executor is called
session/tmux/session_recovery_test.go:
- testSessionRecoveryWithRealTmux: use isolated -L socket to prevent cross-test tmux contamination
* feat(queue): add Score/DiffSummary/TestResults/RetryHistory types for crew autonomy
Add shared queue types needed by the crew autonomy package:
- Score: assembled quality gate results from a Lookout sweep
- TestResults: test run outcome (pass/fail, output, duration)
- DiffSummary: git diff statistics at sweep time (with Excerpt field)
- RetryHistory/RetryAttempt: correction loop attempt tracking
- ReviewItem.Score: attach Sweep results to review queue items
Also commit:
- AskUserQuestion integration test (TestApprovalFlow_AskUserQuestion_ImmediateAllow)
- session_service bench test updates
- Makefile, CLAUDE.md, .gitignore meta updates
- E2E benchmark test additions
* Update Makefile (#14)
Small fix per https://github.com/jtbonhomme/go-nilcheck/pull/4/changes
* Tidy (#19)
- Update gitignore
- Remove coverage.out from git
- Improve light mode colors
* feat: checkpoint system with fork, history detection, and socket registry (#18)
* test(classifier): update AskUserQuestion expected category to ToolCategoryBuiltin
AskUserQuestion was intentionally removed from builtinAgentTools so it
escalates to the review queue. Update the test to reflect the new expected
category instead of ToolCategoryBuiltinAgent.
* perf(session): fix tight loop and GC pressure in waitForCommandOrDrain
Two bugs caused ~294% CPU usage when a CommandExecutor's response channel
was closed (e.g. tmux session stopped):
1. time.After(1s) inside the for loop allocated a new timer+channel on
every iteration, leaking ~18 million timers/sec under load.
2. When responseCh was closed, the select case returned immediately with
ok=false, but the caller (executionLoop) ignored the return and kept
calling waitForCommandOrDrain in a tight infinite spin.
Fix both:
- Move time.NewTimer outside the loop with defer Stop()
- Return bool from waitForCommandOrDrain (false = channel closed)
- Exit executionLoop when false is returned
CPU usage dropped from ~294% to ~2%.
* feat(ui): show relative timestamps on notification toasts and prune stale entries
- Replace absolute locale time with a live relative timestamp (e.g. "just
now", "30s ago", "2 mins ago") that ticks every second; the full time
is preserved as a hover tooltip
- Purge notifications older than 5 minutes from state every minute so
stale toasts do not accumulate in the panel indefinitely
* docs: Add feature plan for performance-benchmarking
Implementation plan covering 6 stories across Go CI benchmark workflow,
ConnectRPC handler benchmarks, terminal pipeline benchmarks, frontend
bundle/throughput gates, profiling runbook, and E2E latency measurement.
Includes 3 ADRs:
- ADR-001: CI benchmark storage (benchmarks branch + dual thresholds)
- ADR-002: Tiered benchmark organization (Tier 1-3 execution strategy)
- ADR-003: Frontend measurement strategy (size-limit + Playwright throughput)
* feat(benchmarks): implement comprehensive performance benchmarking system
Adds automated regression detection for Go, frontend, and E2E layers:
Go benchmarks:
- CI workflow (.github/workflows/benchmark.yml) with 3-tier execution
- Tier 1: critical-path benchmarks on every PR (alert 120%, fail 150%)
- Tier 2: full suite on push to main
- Tracks baselines on dedicated 'benchmarks' branch via github-action-benchmark
- server/terminal/delta_bench_test.go: 5 realistic ANSI benchmarks
(100KB payloads, rapid sequential updates, progress bars, 200x50 screens)
- session/scrollback/buffer_bench_test.go: 4 concurrency benchmarks
(GOMAXPROCS concurrent read/write, burst eviction, large buffer GetLastN)
- server/services/session_service_bench_test.go: 3 ConnectRPC benchmarks
(ListSessions empty, 50-session load, GetSession single retrieval)
Frontend benchmarks:
- web-app/.size-limit.json: bundle size regression gate (5MB total, 650KB React)
- web-app/lighthouserc.json: advisory Lighthouse CI (warn-only, never blocks)
- web-app/tests/e2e/benchmarks/terminal-throughput.spec.ts: xterm.js throughput
(10 runs, 2 warmup discards, single evaluate() timing to eliminate IPC overhead)
- web-app/tests/e2e/benchmarks/rpc-latency.spec.ts: ListSessions TTFB + total latency
- web-app/tests/e2e/benchmarks/output-benchmark-results.ts: shared result formatter
Makefile / tooling:
- benchmark-baseline, benchmark-compare (benchstat A/B), benchmark-tier1
- profile-goroutines, profile-block, profile-mutex, profile-trace targets
- benchstat added to install-tools
- bench-*.txt added to .gitignore
Documentation:
- docs/PROFILING.md: 506-line runbook covering CPU, heap, goroutine, block,
mutex, trace, benchmark profiling, and Speedscope (with copy-paste commands)
Verified: all Go benchmarks compile and run (5120 MB/s delta, 1.58ms ListSessions)
* fix(benchmarks): include new delta benchmarks in Tier 1 and add backend readiness probe
- Broaden Tier 1 regex from `BenchmarkDeltaGeneration` to `BenchmarkDeltaGenerat`
so it matches both the existing BenchmarkDeltaGeneration (delta_test.go) and
all new BenchmarkDeltaGenerator_* benchmarks (delta_bench_test.go)
- Add `npx wait-on http://localhost:8543 --timeout 30000` after starting the
backend in the e2e-latency CI job, preventing a race where Playwright fires
before the server is ready to accept connections
* fix(benchmarks): address code quality, security, and ops review findings
Go test code:
- DRY: expose repo on benchServiceFixture; eliminate duplicated setup in
ListSessions_50Sessions and GetSession benchmarks
- Dead code: remove unused runtime.MemStats declaration and ReadMemStats call
- Magic number: replace literal 55 with named constant warmupIters in
delta_bench_test.go with explaining comment (fullSyncInterval+5)
TypeScript tests:
- rpc-latency: fix misleading comment about page.evaluate IPC overhead
- terminal-throughput: document that CI uses canvas renderer (no GPU) so
baselines are renderer-specific and not cross-comparable with local WebGL
CI workflow:
- Security: move permissions from global scope to per-job; frontend-bundle
and lighthouse no longer hold contents:write or pull-requests:write
- Security: SHA-pin benchmark-action/github-action-benchmark@v1 to
a60cea5bc7b49e15c1f58f411161f99e0df48372 (v1.22.0) to prevent tag drift
- Ops: run npx wait-on from web-app/ so it uses the locally installed package
instead of downloading from npm on each CI run
package.json:
- Add wait-on@^8.0.1 as devDependency for deterministic CI installs
* feat(session-resumption): checkpoint system with fork, history detection, and socket registry
- **ProcessInspector** (`session/procinfo/`): reads /proc or sysctl to locate Claude conversation
files from a running session process
- **HistoryFileDetector** (`session/history_detector.go`): discovers Claude JSONL history files
by inspecting the process environment and standard paths
- **HistoryFileWatcher** (`session/history_watcher.go`): watches the history file for changes
and tracks the current conversation UUID
- **Checkpoint struct** (`session/checkpoint.go`): stores git SHA, conversation UUID, line count,
and scrollback sequence at a named point in time
- **CreateCheckpoint** on Instance: captures current git SHA (via worktree), conversation UUID
(via claudeSession), and line count by scanning the history file
- **CaptureCurrentState**: cold-restore path that saves checkpoint on session shutdown so
resumption can pick up where it left off
- **ForkScrollback** (`session/scrollback/fork.go`): copies scrollback entries up to a given
sequence number, handles compressed sources (gzip/zstd), uses 0600 permissions on temp files
- **ForkClaudeConversation** (`session/history_fork.go`): copies the first N lines of a Claude
conversation file to a new UUID-named file, enabling `--resume` from a checkpoint
- **NewGitWorktreeFromCommitSHA**: creates a git worktree pinned to a specific commit SHA so
forked sessions start from the exact checkpoint state
- **ForkFromCheckpoint** on Instance: orchestrates scrollback fork + conversation fork + git
worktree to produce a fully configured, unstarted Instance ready for `Start(true)`
- **ForkSession RPC** (`server/services/session_service.go`): ConnectRPC handler that validates
inputs (including path traversal protection), forks the session, persists it, and starts it
asynchronously; uses `GetCheckpoints()` thread-safe accessor
- **forkSession hook** (`web-app/src/lib/hooks/useSessionService.ts`): TypeScript client hook
- **Fork UI** (`web-app/src/components/sessions/SessionCard.tsx`): checkpoint + fork dialogs with
ARIA roles, error states, Escape key handling, and CSS module classes
- **SocketRegistry** (`session/mux/socket_registry.go`): file-backed JSON registry for fast
socket reconnection after restart; wired in `server/dependencies.go`
- **retryWithDelay** (renamed from backoffRetry): fixed-delay retry with immediate skip on
ECONNREFUSED; `registryStaleThreshold` named constant for 24h stale cutoff
- **Input isolation test** for multiplexer
- `signal.NotifyContext` replaces `context.Background()` in rootCmd.RunE so Ctrl-C/SIGTERM
cancel the server context and trigger the shutdown hook (CaptureCurrentState); remove the
forced os.Exit(1) goroutine that was bypassing it
* ci: add proto generation and web dist steps to all CI workflows
Generated code in gen/ and web-app/src/gen/ is gitignored but required
at compile time. server/web/dist is also gitignored but needed for the
Go embed directive. Add buf + npm proto generation steps to every
workflow job before any Go or Next.js compilation.
- build.yml: generate protos and stub dist before go test and go build
- lint.yml: generate protos and stub dist before golangci-lint
- benchmark.yml go-tier1/tier2: generate protos and stub dist
- benchmark.yml frontend-bundle/throughput/lighthouse: generate protos
(TypeScript) before npm run build
- benchmark.yml e2e-latency: generate protos, build Next.js, copy dist,
then build Go binary (fixed step ordering)
* ci: fix buf generate running from wrong directory
Split npm ci and buf generate proto into separate steps so buf runs
from the repo root (where buf.gen.yaml lives) rather than from web-app/
after the cd. Use working-directory: web-app for the npm ci step.
* fix(ci): upgrade golangci-lint to v2, pin Go 1.25, and fix Linux build
- session/procinfo/inspector_other.go: add !darwin build stub using gopsutil
for Linux/Windows — fixes 'build constraints exclude all Go files' on CI
- session/tmux/session_recovery_test.go: use sibling dirs (current-dir vs
session-worktree) instead of parent/child to prevent NotContains assertion
spuriously failing when currentDir is a substring of worktreeDir path
- .golangci.yml: migrate from v1 to v2 config format (version: "2",
linters-settings → linters.settings, issues.exclude-rules →
linters.exclusions.rules)
- .github/workflows/lint.yml: pin go-version to '1.25.0' (matching go.mod)
and upgrade golangci-lint to latest (v2) — fixes typecheck failures caused
by golangci-lint v1.60.1 being incompatible with Go 1.25 export format;
remove v1-only flags --fast and --out-format=line-number
* fix(ci): pin golangci-lint v2.11.4, Go 1.25, fix darwin cross-compile, persist session_id from hooks
- lint.yml: pin golangci-lint to v2.11.4 (built with go1.26.1, supports go 1.25 targets);
'version: latest' was resolving to v1.64.8 (go1.24) which rejected go 1.25 module directives
- build.yml: upgrade setup-go to v5 and go-version to 1.25.0 to match go.mod minimum
- procinfo: add 'cgo' constraint to openfiles_darwin.go so it only compiles when CGO is
available; add openfiles_darwin_nocgo.go stub for cross-compilation (darwin/amd64 from Linux)
- approval_handler: persist payload.SessionID + TranscriptPath into Instance.SetHistoryInfo
on first PermissionRequest hook — enables --resume on session restart without waiting for
the HistoryLinker filesystem watcher
* fix(ci): upgrade to golangci-lint-action@v7 and exclude multiplexer from Windows builds
- lint.yml: action v6 rejects golangci-lint v2.x; upgrade to v7 which supports v2
- session/mux/multiplexer.go: add !windows build constraint — syscall.SIGWINCH is
undefined on Windows; the PTY multiplexer is Unix-only and no server code uses
the Multiplexer type directly (only cmd/claude-mux does)
* fix(lint): rename forbidigo 'p' to 'pattern' for golangci-lint v2 schema
* fix(lint): suppress errcheck defaults and fix nilnil/staticcheck violations
- Add disable-all: true to .golangci.yml so only the 5 explicitly
listed linters run (forbidigo, ineffassign, nilnil, staticcheck, govet).
golangci-lint v2 enables errcheck by default which was not the original
intent of the config.
- Add //nolint:nilnil to intentional nil,nil returns in Detect() —
these signal "nothing found, not an error" which is correct API behaviour.
- Replace buf.WriteString(fmt.Sprintf(...)) with fmt.Fprintf in
delta_bench_test.go to satisfy staticcheck QF1012.
* fix(lint): use default: none to restrict to explicit linter list
golangci-lint v2 changed disable-all: true to default: none.
This ensures only the 5 configured linters run (forbidigo, ineffassign,
nilnil, staticcheck, govet) and not the v2 default set which includes
errcheck.
* style: run gofmt on all Go files
Apply gofmt formatting uniformly across the codebase. These are
whitespace/formatting-only changes with no logic differences.
* chore: remove benchmark workflow and test files from session-resumption branch
* chore: remove performance-benchmarking plan docs from session-resumption branch
---------
Co-authored-by: Tyler Stapler <tystapler@gmail.com>
* fix(debug-menu): use correct CSS variables so menu is not transparent (#20)
DebugMenu.module.css was referencing undefined --color-* variables
(e.g. --color-bg, --color-border) which defaulted to transparent.
Replaced all references with the actual variables defined in globals.css
(--modal-background, --border-color, --text-primary, etc).
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(benchmarks): comprehensive Go performance benchmarking with CI regression gate (#17)
* test(classifier): update AskUserQuestion expected category to ToolCategoryBuiltin
AskUserQuestion was intentionally removed from builtinAgentTools so it
escalates to the review queue. Update the test to reflect the new expected
category instead of ToolCategoryBuiltinAgent.
* perf(session): fix tight loop and GC pressure in waitForCommandOrDrain
Two bugs caused ~294% CPU usage when a CommandExecutor's response channel
was closed (e.g. tmux session stopped):
1. time.After(1s) inside the for loop allocated a new timer+channel on
every iteration, leaking ~18 million timers/sec under load.
2. When responseCh was closed, the select case returned immediately with
ok=false, but the caller (executionLoop) ignored the return and kept
calling waitForCommandOrDrain in a tight infinite spin.
Fix both:
- Move time.NewTimer outside the loop with defer Stop()
- Return bool from waitForCommandOrDrain (false = channel closed)
- Exit executionLoop when false is returned
CPU usage dropped from ~294% to ~2%.
* feat(ui): show relative timestamps on notification toasts and prune stale entries
- Replace absolute locale time with a live relative timestamp (e.g. "just
now", "30s ago", "2 mins ago") that ticks every second; the full time
is preserved as a hover tooltip
- Purge notifications older than 5 minutes from state every minute so
stale toasts do not accumulate in the panel indefinitely
* docs: Add feature plan for performance-benchmarking
Implementation plan covering 6 stories across Go CI benchmark workflow,
ConnectRPC handler benchmarks, terminal pipeline benchmarks, frontend
bundle/throughput gates, profiling runbook, and E2E latency measurement.
Includes 3 ADRs:
- ADR-001: CI benchmark storage (benchmarks branch + dual thresholds)
- ADR-002: Tiered benchmark organization (Tier 1-3 execution strategy)
- ADR-003: Frontend measurement strategy (size-limit + Playwright throughput)
* feat(benchmarks): implement comprehensive performance benchmarking system
Adds automated regression detection for Go, frontend, and E2E layers:
Go benchmarks:
- CI workflow (.github/workflows/benchmark.yml) with 3-tier execution
- Tier 1: critical-path benchmarks on every PR (alert 120%, fail 150%)
- Tier 2: full suite on push to main
- Tracks baselines on dedicated 'benchmarks' branch via github-action-benchmark
- server/terminal/delta_bench_test.go: 5 realistic ANSI benchmarks
(100KB payloads, rapid sequential updates, progress bars, 200x50 screens)
- session/scrollback/buffer_bench_test.go: 4 concurrency benchmarks
(GOMAXPROCS concurrent read/write, burst eviction, large buffer GetLastN)
- server/services/session_service_bench_test.go: 3 ConnectRPC benchmarks
(ListSessions empty, 50-session load, GetSession single retrieval)
Frontend benchmarks:
- web-app/.size-limit.json: bundle size regression gate (5MB total, 650KB React)
- web-app/lighthouserc.json: advisory Lighthouse CI (warn-only, never blocks)
- web-app/tests/e2e/benchmarks/terminal-throughput.spec.ts: xterm.js throughput
(10 runs, 2 warmup discards, single evaluate() timing to eliminate IPC overhead)
- web-app/tests/e2e/benchmarks/rpc-latency.spec.ts: ListSessions TTFB + total latency
- web-app/tests/e2e/benchmarks/output-benchmark-results.ts: shared result formatter
Makefile / tooling:
- benchmark-baseline, benchmark-compare (benchstat A/B), benchmark-tier1
- profile-goroutines, profile-block, profile-mutex, profile-trace targets
- benchstat added to install-tools
- bench-*.txt added to .gitignore
Documentation:
- docs/PROFILING.md: 506-line runbook covering CPU, heap, goroutine, block,
mutex, trace, benchmark profiling, and Speedscope (with copy-paste commands)
Verified: all Go benchmarks compile and run (5120 MB/s delta, 1.58ms ListSessions)
* fix(benchmarks): include new delta benchmarks in Tier 1 and add backend readiness probe
- Broaden Tier 1 regex from `BenchmarkDeltaGeneration` to `BenchmarkDeltaGenerat`
so it matches both the existing BenchmarkDeltaGeneration (delta_test.go) and
all new BenchmarkDeltaGenerator_* benchmarks (delta_bench_test.go)
- Add `npx wait-on http://localhost:8543 --timeout 30000` after starting the
backend in the e2e-latency CI job, preventing a race where Playwright fires
before the server is ready to accept connections
* fix(benchmarks): address code quality, security, and ops review findings
Go test code:
- DRY: expose repo on benchServiceFixture; eliminate duplicated setup in
ListSessions_50Sessions and GetSession benchmarks
- Dead code: remove unused runtime.MemStats declaration and ReadMemStats call
- Magic number: replace literal 55 with named constant warmupIters in
delta_bench_test.go with explaining comment (fullSyncInterval+5)
TypeScript tests:
- rpc-latency: fix misleading comment about page.evaluate IPC overhead
- terminal-throughput: document that CI uses canvas renderer (no GPU) so
baselines are renderer-specific and not cross-comparable with local WebGL
CI workflow:
- Security: move permissions from global scope to per-job; frontend-bundle
and lighthouse no longer hold contents:write or pull-requests:write
- Security: SHA-pin benchmark-action/github-action-benchmark@v1 to
a60cea5bc7b49e15c1f58f411161f99e0df48372 (v1.22.0) to prevent tag drift
- Ops: run npx wait-on from web-app/ so it uses the locally installed package
instead of downloading from npm on each CI run
package.json:
- Add wait-on@^8.0.1 as devDependency for deterministic CI installs
* ci(bench): replace benchmark-action with file-based baselines in main
Drop the separate benchmarks branch (gh-pages style) in favour of
committing baseline files directly to main under benchmarks/:
benchmarks/go/tier1-baseline.txt
benchmarks/go/tier2-baseline.txt
benchmarks/frontend/throughput-baseline.json
benchmarks/e2e/latency-baseline.json
On every push to main each job copies its output to the corresponding
baseline file, commits, then does git pull --rebase && git push to
handle concurrent tier1/tier2 updates without races.
On pull requests each job compares the current run against the committed
baseline (benchstat for Go, inline Node.js diff for JSON metrics) and
creates or updates a marker-based PR comment so the comparison is always
visible in context.
Also updates docs/PROFILING.md recovery runbook to reference the new
file paths instead of the retired benchmarks branch.
* fix(benchmarks): address shipping gate blocking issues
- NotificationContext: exempt approval_needed/question from stale toast
pruning — actionable notifications that block Claude must not silently
vanish after 5 minutes
- delta_bench_test: rotate through 50 content variants in
BenchmarkDeltaGenerator_LargeANSI_100KB so every iteration exercises
real delta computation instead of the trivial no-change fast path
- rpc-latency.spec.ts: make BACKEND_URL configurable via env var
(defaults to localhost:8543; CI can override)
- output-benchmark-results.ts: remove stale benchmark-action JSDoc
references — CI uses file-based baseline comparison, not that action
- package-lock.json: add size-limit and wait-on dev deps for benchmark CI
* fix(ci): harden benchmark workflow security and ops
Actions pinned to commit SHAs to prevent supply-chain drift:
- actions/checkout@v4 → 34e114876b0b
- actions/setup-go@v5 → 40f1582b2485
- actions/setup-node@v4 → 49933ea5288c
- actions/github-script@v7 → f28e40c7f34b
- actions/upload-artifact@v4 → ea165f8d65b6
Go toolchain:
- go-version: '1.23' → go-version-file: 'go.mod' (tracks actual module
requirement instead of hard-coded older version)
- benchstat pinned to v0.0.0-20260312031701-16a31bc5fbd0 (prevents
output-format changes silently breaking the regression grep)
Ops:
- cancel-in-progress: true → ${{ github.ref != 'refs/heads/main' }}
so PR runs cancel on supersede but main baseline-commit steps
are never interrupted mid-flight
Docs:
- ADR-001 marked Superseded with an implementation note explaining
why the final design deviates from the original benchmarks-branch
+ github-action-benchmark plan
* fix(auth): resolve passkey rpID mismatch on macOS and fix layout overlap tests
On macOS, /etc/resolv.conf is advisory and not consulted for DNS resolution.
getDNSSearchDomains now also runs `scutil --dns` to get search domains from
the system authority, so hostnames like fbg-*.staplerhome.internal are
correctly added to the WebAuthn rpID list at startup.
Also suppress the global ConditionalHeader on /test/* routes so the
layout-overlap Playwright test page measures a single header, matching
the modal's paddingTop assumptions. All 12 overlap tests now pass.
* test(layout): add Playwright overlap tests for session modal and header
Adds a no-backend test harness page at /test/layout-overlap (sessions and
?mode=rq variants) and 12 Playwright tests that verify:
- modal-content top is always below the sticky app-header
- modal-content fits within the viewport vertically
- all five session tabs are always visible
- all seven terminal toolbar buttons are always visible
* feat(install): add homebrew tap support and fix install docs
- Convert this repo into a homebrew tap (Formula/ written by goreleaser)
- Use GITHUB_TOKEN for formula pushes; remove unused BREW_TOKEN/HOMEBREW_REPO_TOKEN
- Add ssq symlink to goreleaser brew formula so both names install
- Fix install.sh: clear error message when no releases exist instead of raw API dump
- Update README: correct brew tap command (URL-based), fix Building from Source prereqs
- Rewrite CONTRIBUTING.md with accurate Homebrew-first dev setup steps
* fix(ci): add protobuf generation step to frontend benchmark jobs
Generated TypeScript files (web-app/src/gen/) are gitignored and must
be produced at build time. All four frontend jobs were running npm run build
without first running buf generate proto, causing 'Module not found' errors
for @/gen/session/v1/session_pb and related imports.
Adds buf-setup-action + buf generate proto after npm ci in:
- frontend-bundle
- frontend-throughput
- lighthouse
- e2e-latency
Also fixes e2e-latency's Go build which imports gen/proto/go/ (also gitignored).
* fix(ci): add proto generation to build and lint workflows
The gen/ directory is gitignored, so build/lint/test all require
buf generate proto to run before any Go compilation.
build.yml — restructured into three jobs:
- prepare: npm ci → buf generate → web UI build → upload artifacts
- test: downloads artifacts, runs go test (linux/amd64 only)
- build: downloads artifacts, cross-compiles matrix (not gated by test)
Binaries are now always uploaded regardless of test failures.
Added CGO_ENABLED=0 to cross-compilation builds.
Added proto/** and web-app paths to trigger filters.
lint.yml — added before golangci-lint:
- npm ci (needed for protoc-gen-es local plugin)
- buf generate proto (produces gen/proto/go/)
- mkdir server/web/dist placeholder (satisfies //go:embed all:dist
without a full Next.js build)
* fix(ci): fix web dist copy path and size-limit config
build.yml: split web UI build into two steps so cp runs from repo root.
The single-shell `cd web-app && ... && cp -r web-app/out` was resolving
web-app/out relative to web-app/, not the workspace root.
web-app/package.json: restore size-limit config without the `ignore` field.
The `ignore` option requires @size-limit/webpack or @size-limit/esbuild;
only @size-limit/file is installed. The `ignore` was redundant anyway since
the path glob `**/*.js` already excludes .map files.
* fix(ci): use 'buf generate' not 'buf generate proto'
buf.gen.yaml lives at the repo root and already declares
'inputs: - directory: proto'. Passing 'proto' as a positional
argument makes buf look for buf.gen.yaml inside proto/, which
doesn't exist. Plain 'buf generate' reads from the repo root.
Fixes all 3 failing checks: Build, Lint, Frontend Benchmarks.
* fix(ci): fix static server and web dist ordering in benchmark jobs
frontend-throughput: replace 'npm run start' (next start) with
'npx serve out -l 3001' — next start does not work with output:export.
e2e-latency: move Next.js build + dist copy before 'go build' so that
server/web/embed.go can find server/web/dist/ at compile time.
Also fix frontend server to use npx serve instead of next start.
* fix(build): fix Windows cross-compilation and benchmark CI issues
session/mux: syscall.SIGWINCH is undefined on Windows. Extract into
platform-specific helpers (signals_unix.go / signals_windows.go) so
the Windows cross-compilation target builds cleanly. Verified with
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build.
benchmark.yml:
- frontend-throughput: remove manual 'Start server' step — Playwright's
webServer config already starts 'next dev' automatically; the extra
serve step was on the wrong port (3001 vs 3333) and had no effect.
- e2e-latency: remove working-directory:web-app from 'Start backend
server' — the stapler-squad binary is at the repo root, not web-app/.
* fix(ci): fix benchmark output paths and e2e CORS failure
- Fix benchmark output paths: path.resolve(__dirname, '../../...') from
web-app/tests/e2e/benchmarks/ resolved to web-app/tests/, not web-app/.
Changed to '../../../' so results land where CI expects them.
- Fix e2e-latency CORS failure: remove unused 'Start frontend server' step
(served on :3001, Playwright used :3333). Add TEST_PORT=8543 and
TEST_REUSE_SERVER=1 so Playwright reuses the Go backend directly —
making the ListSessions fetch same-origin and bypassing CORS entirely.
- Add TEST_REUSE_SERVER env var support to playwright.config.ts.
* fix(ci): add /api prefix to ConnectRPC path in rpc-latency benchmark
All ConnectRPC handlers are registered under /api/... but the test
was fetching /session.v1.SessionService/ListSessions, which the static
file server caught and returned an HTML 404 page.
* fix(ci): fix lint and rpc-latency benchmark failures
- delta_bench_test.go: replace WriteString(fmt.Sprintf()) with
fmt.Fprintf() to satisfy QF1012 staticcheck rule
- rpc-latency.spec.ts: replace response.timing() with performance.now()
inside page.evaluate(). response.timing() is not available for
same-origin fetch requests; measuring inside the page avoids the
API entirely and eliminates Playwright IPC overhead on timing.
* fix(ci): add missing checkpoint system code and update lint/build workflows
- Add gopsutil/v3 dependency required by session/procinfo/inspector_other.go
- Add missing checkpoint methods to Instance (CreateCheckpoint, ForkFromCheckpoint, GetCheckpoints)
- Add missing fields (Checkpoints, ActiveCheckpoint, ForkedFromID, HistoryFilePath) to Instance and InstanceData
- Add GetPanePID to TmuxProcessManager and TmuxSession
- Add GetCurrentCommitSHA to GitWorktreeManager
- Add NewGitWorktreeFromCommitSHA to git package
- Add retryWithDelay/isConnectionRefused helpers to external_discovery.go
- Add ForkSession, CreateCheckpoint, ListCheckpoints RPCs to proto and SessionService
- Update lint.yml: golangci-lint-action v6→v7, version v1.60.1→v2.11.4, go 1.23→1.25.0, node 20→22
- Refactor build.yml to use composite action .github/actions/prepare
* fix(lint): exclude windows-only tmux_windows package from golangci-lint on Linux
golangci-lint v2 fails to analyze session/tmux/tmux_windows/ on Linux because
the package has //go:build windows and contains no files satisfying the build
constraint. Exclude the directory to prevent cross-package analysis failure.
* fix(ci): move exclude-dirs to config and fix test path prefix collision
- Move --exclude-dirs=session/tmux/tmux_windows from CLI args to
.golangci.yml since golangci-lint v2 removed the CLI flag
- Fix TestSessionRecoveryWorkflowEnd2End/CompareOldVsNewRestoreBehavior
where worktreeDir was a subdirectory of differentDir (currentDir),
causing NotContains(cmd, currentDir) to fail spuriously; use a
separate t.TempDir() for differentDir to eliminate the prefix relationship
* fix(ci): migrate golangci-lint config to v2 format
Add required 'version: 2' field and move linters-settings under
linters.settings to comply with golangci-lint v2 schema.
* fix(ci): fix golangci-lint v2 config schema
- rename run.exclude-dirs -> linters.exclusions.paths
- rename issues.exclude-rules -> linters.exclusions.rules
- rename forbidigo p: -> pattern: (mandatory in v2)
- remove now-empty run and issues top-level blocks
* fix(lint): check errcheck errors in test cleanup and IndexMessage calls
* style: apply gofmt to entire codebase
Run gofmt -w . to fix alignment and whitespace in 166 files.
Required to pass the 'Check formatting' CI step in lint.yml.
* fix(ci): replace buf-setup-action with curl install in benchmarks and composite action
bufbuild/buf-setup-action@v1 hits GitHub API rate limits without a token.
Replace all occurrences with direct curl download.
Also fix buf generate -> buf generate proto.
* docs: document semver label requirement for PRs
---------
Co-authored-by: Tyler Stapler <tystapler@gmail.com>
* chore(bench): update frontend throughput baseline [skip ci]
* chore(bench): update go tier1 baseline [skip ci]
* chore(bench): update go tier2 baseline [skip ci]
* ci: fix auto-tag → release trigger and use official buf action (#22)
* ci(prepare): use bufbuild/buf-setup-action instead of curl install
* ci(release): trigger release workflow from auto-tag via workflow_dispatch
GITHUB_TOKEN pushes don't trigger other workflows (GitHub security
restriction). Add workflow_dispatch to release.yml and dispatch it
explicitly from auto-tag.yml after pushing the tag.
* ci: add PR template with semver label reminder
---------
Co-authored-by: Tyler Stapler <tystapler@gmail.com>
* chore(bench): update frontend throughput baseline [skip ci]
* chore(bench): update go tier1 baseline [skip ci]
* chore(bench): update go tier2 baseline [skip ci]
* fix(ci): remove gh workflow run, rely on PAT tag push to trigger release
GITHUB_TOKEN cannot dispatch workflow_dispatch events on other workflows
(GitHub security restriction). Remove the failing step entirely.
When RELEASE_TOKEN (a PAT with repo+workflow scopes) is set, the tag push
in the previous step triggers release.yml naturally via its push.tags trigger.
Action required: add RELEASE_TOKEN secret (PAT with repo+workflow scopes)
at Settings → Secrets → Actions.
* fix(classifier): add path expansion and PathMatcher primitives (#21)
* fix(classifier): add path expansion and PathMatcher primitives
Replace the fragile regex-based path check in seed-deny-rm-rf-root with
structured path-semantic primitives that expand tilde and environment
variables before classification.
Problem: the old regex `/|~|\$HOME` matched any absolute path (including
`/tmp/ai-setup-test`) because `/` matched the start of every absolute path.
Changes:
- Add `ExpandPath(arg)` — expands `~`, `~/foo`, `$HOME`, `$VAR` via
os.ExpandEnv, then filepath.Clean for absolute paths
- Add `ClassifyPath(path, ctx) PathClassification` — bitmask returning
PathRoot, PathHome, PathSystemDir, PathTempDir, PathCwd, PathGitRepo
- Add `PathMatcher` struct to Rule — ArgIndex, MatchIf, RejectIf bitmasks;
works alongside CommandPattern/Criteria with AND semantics
- Add `ExpandedArgs []string` to ParsedCommand — populated eagerly in
ExtractAllCommands via ExpandPath
- Thread ClassificationContext through classifySingle/classifyCompound/
matchesRule so PathMatcher has access to Cwd and RepoRoot
- Rewrite seed-deny-rm-rf-root to use PathMatcher{MatchIf: PathRoot|PathHome}
instead of the combined regex — rm -rf $HOME now correctly denied via
expansion; rm -rf $HOME/subdir correctly passes
Also:
- Fix Makefile: add proto-gen dependency to test, test-verbose, test-coverage,
vet, lint, and benchmark targets so they work standalone without make build
- Fix PROTO_OUT_DIRS variable (web/src/gen → web-app/src/gen)
- Update CLAUDE.md testing section to reflect make build requirement
Tests: 79 classifier tests pass (11 new), 22 path_matcher unit tests added
* fix(classifier): prevent heredoc/string-data false positives with durable tests
command_parser.go
- Remove the post-parse fallback in ExtractAllCommands. When mvdan.cc/sh parses
a command successfully but finds no CallExpr nodes (pure variable assignment
like WARNING="value", or pure redirection), return empty ParsedCommand slice.
The raw-string fallback was mis-constructing ParsedCommand entries whose Raw
field contained the full statement text, allowing patterns to match inside
string literals. The fallback now only fires when the parser itself fails.
classifier.go
- Parse command once and reuse across CommandPattern, Criteria, and PathMatcher
checks (lazy init, avoids duplicate ExtractAllCommands calls).
- Add inline comment explaining why CommandPattern matches the full raw string
(redirect operators like >> are not in CallExpr args) and the two-gate design:
rules that pair CommandPattern with PathMatcher rely on PathMatcher to reject
false positives where pattern text appears in non-executed contexts.
classifier_test.go
- Add TestClassify_RmRf_StringData_NotDenied (8 cases):
* single-quoted heredoc body with rm -rf /
* gh pr create with heredoc body containing rm -rf /
* echo with rm -rf / as string argument
* printf with rm-rf / as argument
* shell comment containing rm -rf /
* cat heredoc-to-file with rm -rf / in body
* variable assignment containing rm -rf /
All 8 pass: PathMatcher rejects the outer command whose args do not contain
a root or home path; variable assignments yield empty parsedCmds from the
parser-success-no-callables path.
All 156 services tests pass.
* style: fix gofmt alignment in main.go var block
---------
Co-authored-by: Tyler Stapler <tystapler@gmail.com>
* chore: bump version to 1.1.2 [skip ci]
* chore(bench): update frontend throughput baseline [skip ci]
* chore(bench): update go tier1 baseline [skip ci]
* chore(bench): update go tier2 baseline [skip ci]
* merge: finalize resolution of remaining conflicts in proto and web-app
* merge: incorporate latest upstream changes and resolve regressions
This commit finalizes the integration of performance benchmarking, tmux resilience, and checkpointing while fixing minor behavioral regressions in detection patterns and error formatting.
* chore: rename Claude Squad to Stapler Squad in nil_safety_check.sh
Agent-Logs-Url: https://github.com/tstapler/stapler-squad/sessions/233ef675-a8ac-4957-a2df-86b348c680cd
Co-authored-by: tstapler <3860386+tstapler@users.noreply.github.com>
* fix: upgrade to golangci-lint v2 and resolve fanatics fork merge issues
After merging upstream changes from the fanatics fork, the build was
broken because golangci-lint v1 was installed but .golangci.yml targets
v2. Installing v2 surfaced ~30 lint issues that were previously silent.
Key changes:
- Remove `gosimple` linter from config (merged into `staticcheck` in v2)
- Exclude web-app/ and session/mux/picker.go from lint paths
- Fix detection package tests to match current API (programs map → flat
struct, add DetectForProgram shim)
- Fix printf format arg in tmux.go
- Address all staticcheck, unused, and typecheck findings across 35 files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: resolve merge conflict markers and CSS syntax errors
- Resolve all merge conflict markers in CI workflows (auto-tag.yml,
benchmark.yml, label-check.yml) — prefer upstream-fanatics versions
with pinned SHA action refs, go-version-file, buf generation steps,
and --autostash on rebase
- Fix 9 CSS double-parenthesis errors (var(--x))) across Header,
WorkspaceSwitcher, and escape-codes page modules
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address Copilot review feedback on code quality
- path_completion_service: use filepath.Dir/Base instead of hardcoded
"/" separator; fix pathExists to report true for files not just dirs
- history_linker: return linker with nil watcher when UserHomeDir fails
instead of watching a relative path
- Makefile: install golangci-lint v2 (not v1) in fallback path
- server.go: use typed enum constants for notification type/priority
instead of raw int32 literals
- etag_cache: update cache on he…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR implements the Permissions Extension (MDD) by modularizing the command classification logic and providing standalone hooks for multiple AI coding agents.
Key Changes:
server/services/to a new top-levelpkg/classifier/package for reuse across different binaries.ssq-hooks) that providescheck,serve,proxy, andinstallsubcommands for tool-agnostic permissions management.geminiandopen-codeinscripts/hooks/, enabling consistent security policies across Claude, Gemini, and Open Code.WriteSessionUserOptionsandScanByUserOptionsusing tmux user options to ensure session metadata (like target CLI and categories) persists across server restarts.pkg/classifierpackage.BUG-009(Session Package Test Failures).Success Criteria:
pkg/classifierexists and is used bystapler-squadserver.ssq-hooksbinary is built and functional.geminiandopen-codecalls are intercepted viassq-hookswrappers.