docs: RFC 0049 synchronous guard command
Emerged from alignment dialogue with 5 experts (unanimous convergence). Problem: guard command runs async within tokio::main, causing hangs when invoked from Claude Code hooks. Solution: Run guard synchronously before tokio runtime initialization. Pre-init gates should not depend on post-init infrastructure. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
21dc822d4c
commit
fa98368588
1 changed files with 128 additions and 0 deletions
128
.blue/docs/rfcs/0049-synchronous-guard-command.draft.md
Normal file
128
.blue/docs/rfcs/0049-synchronous-guard-command.draft.md
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
# RFC 0049: Synchronous Guard Command
|
||||
|
||||
**Status**: Draft
|
||||
**Created**: 2026-02-01
|
||||
**Author**: 💙 Judge (via alignment dialogue)
|
||||
**Related**: RFC 0038 (SDLC Workflow Discipline)
|
||||
|
||||
## Problem Statement
|
||||
|
||||
The `blue guard` command validates file writes against worktree rules (RFC 0038). Currently it runs as an async function within `#[tokio::main]`, which causes hanging issues when invoked from Claude Code PreToolUse hooks.
|
||||
|
||||
### Root Cause
|
||||
|
||||
The guard command performs only synchronous operations:
|
||||
- Environment variable checks (`BLUE_BYPASS_WORKTREE`)
|
||||
- Path pattern matching (allowlist)
|
||||
- Filesystem reads (`.git` file/directory)
|
||||
- Subprocess execution (`git branch --show-current`)
|
||||
|
||||
None of these require async, but the tokio runtime initialization adds:
|
||||
1. Thread pool creation overhead
|
||||
2. Potential resource contention in hook contexts
|
||||
3. Failure modes when spawned from non-tokio parent processes
|
||||
|
||||
### Current Workaround
|
||||
|
||||
The guard hook script uses a full path to the binary and closes stdin:
|
||||
```bash
|
||||
/Users/ericg/letemcook/blue/target/release/blue guard --path="$FILE_PATH" </dev/null
|
||||
```
|
||||
|
||||
This works but is fragile (hardcoded path) and doesn't address the architectural issue.
|
||||
|
||||
## Proposed Solution
|
||||
|
||||
Run the guard command synchronously **before** tokio runtime initialization.
|
||||
|
||||
### Implementation
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
// Fast-path: handle guard before tokio
|
||||
if let Some(exit_code) = maybe_handle_guard() {
|
||||
std::process::exit(exit_code);
|
||||
}
|
||||
|
||||
// Normal path: tokio runtime for everything else
|
||||
tokio_main();
|
||||
}
|
||||
|
||||
fn maybe_handle_guard() -> Option<i32> {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
|
||||
if args.len() >= 2 && args[1] == "guard" {
|
||||
let path = args.iter()
|
||||
.find(|a| a.starts_with("--path="))
|
||||
.map(|a| &a[7..]);
|
||||
|
||||
if let Some(path) = path {
|
||||
return Some(run_guard_sync(path));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn run_guard_sync(path: &str) -> i32 {
|
||||
// Synchronous implementation of guard logic
|
||||
// No tokio, no tracing, just the check
|
||||
// ...
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn tokio_main() -> Result<()> {
|
||||
// Existing async main logic
|
||||
}
|
||||
```
|
||||
|
||||
### Benefits
|
||||
|
||||
1. **Eliminates hanging**: No tokio runtime to initialize
|
||||
2. **Faster execution**: Microseconds instead of milliseconds
|
||||
3. **Simpler hook integration**: No stdin/stdout complexity
|
||||
4. **Correct architecture**: Pre-init gates don't depend on post-init infrastructure
|
||||
|
||||
### Changes Required
|
||||
|
||||
1. Add `maybe_handle_guard()` function in `apps/blue-cli/src/main.rs`
|
||||
2. Implement `run_guard_sync()` with same logic as current async version
|
||||
3. Update hook script to use simple `blue guard --path="$FILE_PATH"`
|
||||
4. Remove hardcoded binary path from hook
|
||||
|
||||
## Alignment Dialogue
|
||||
|
||||
This RFC emerged from an alignment dialogue with 5 experts. Key insights:
|
||||
|
||||
| Expert | Perspective |
|
||||
|--------|-------------|
|
||||
| 🧁 Muffin (Systems Architect) | Pre-init gates must not depend on post-init infrastructure |
|
||||
| 🧁 Cupcake (CLI UX Designer) | Fast-path validation gains nothing from async |
|
||||
| 🧁 Scone (DevOps Engineer) | Hook context resource starvation avoided with sync |
|
||||
| 🧁 Eclair (Security Analyst) | Sync prevents runtime initialization deadlock |
|
||||
| 🧁 Donut (Minimalist) | Guard has no actual async work - signature is misleading |
|
||||
|
||||
**Convergence**: Unanimous in Round 0. All experts independently concluded guard should be synchronous.
|
||||
|
||||
## Open Questions
|
||||
|
||||
1. **Future extensibility**: What if guard needs to call daemon APIs later?
|
||||
- Answer: Create a separate async guard command (e.g., `blue guard-async`) if needed
|
||||
|
||||
2. **Pattern consistency**: This makes guard an exception to async-first design
|
||||
- Answer: Pre-flight validation is a legitimate exception case
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
- [ ] Add `maybe_handle_guard()` pre-tokio check
|
||||
- [ ] Implement `run_guard_sync()` with current logic
|
||||
- [ ] Add `is_in_allowlist_sync()` helper
|
||||
- [ ] Add `is_source_code_path_sync()` helper
|
||||
- [ ] Update hook script to remove full path
|
||||
- [ ] Test hook with simplified invocation
|
||||
- [ ] Remove workaround code
|
||||
|
||||
## References
|
||||
|
||||
- RFC 0038: SDLC Workflow Discipline (introduced guard command)
|
||||
- ADR 0014: Alignment Dialogue Agents (used to deliberate this RFC)
|
||||
- Dialogue: `2026-02-01T2214Z-guard-command-architecture.dialogue.recorded.md`
|
||||
Loading…
Reference in a new issue