Skip to content

Commit 433d285

Browse files
solomonneascursoragentcodex
authored
fix(work): stop Grok hooks treating home roster as a project (#537)
* fix(work): stop Grok hooks treating home roster as a project Require .brigade/config.json via shared resolve_wired_target and brigade work resolve-target so ~/.brigade never becomes a brief target (#536). Co-authored-by: Cursor <cursoragent@cursor.com> * chore(docs): regenerate command inventory for work resolve-target * fix(hooks): harden Grok work-loop fallback Co-authored-by: Codex <codex@openai.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Codex <codex@openai.com>
1 parent bd0ce0d commit 433d285

10 files changed

Lines changed: 663 additions & 24 deletions

File tree

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313
show/watch/resume surfaces). Recoverable from git history; see #442 / #471.
1414

1515
### Added
16+
- `brigade work resolve-target --cwd PATH [--harness NAME]` prints the
17+
Brigade-wired project root (requires `.brigade/config.json`) so shell hooks
18+
share Claude's discovery contract instead of matching any `.brigade/` dir.
19+
- Shared `brigade.wiring.resolve_wired_target` helper used by Claude hooks and
20+
the new resolver CLI; optional harness filter.
21+
- Grok work-loop hook templates under `src/brigade/templates/grok/hooks/` that
22+
use `resolve-target`, timeout the session brief, and do not deny edits while
23+
a brief is running (#536).
1624
- Imported `stations/notify` Go module into the Brigade monorepo. Unified release
1725
manifests now enumerate five native components (25 platform assets plus
1826
`component-manifest-v1.json` and `checksums.txt`) with managed resolution
@@ -31,6 +39,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3139
- `brigade mcp sync --user-scope` (and `brigade operator sync-mcp --user-scope`) no longer writes stdio MCP servers into a user-wide client config silently: interactive runs show the destination, stdio count, and the servers-times-sessions process formula and ask for confirmation, non-interactive and `--json` runs require `--allow-global-stdio`, and plan/sync items now carry `transport` and `scope`. (#349)
3240

3341
### Fixed
42+
- Grok/T3 work-loop discovery no longer treats `~/.brigade` (user-level aboyeur
43+
roster) as a project work root. Hooks and `work resolve-target` require
44+
`.brigade/config.json`, so sessions under `$HOME` or unwired dirs do not
45+
background `brigade work brief --target $HOME` (#536).
3446
- `brigade run` no longer dies on the first unparsable plan when the chef's final
3547
message is prose. The corrective plan turn now restates the output contract
3648
("reply with the JSON plan object and nothing else") alongside the parse error,

docs/command-inventory.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ enabled: run `brigade extras on` once, or set `BRIGADE_EXTRAS=1`.
6969
- `brigade untrusted` (extras): 2 command path(s)
7070
- `brigade update`: 1 command path(s)
7171
- `brigade version`: 1 command path(s)
72-
- `brigade work`: 144 command path(s)
72+
- `brigade work`: 145 command path(s)
7373
- `brigade workflow` (extras): 3 command path(s)
7474

7575
## Commands
@@ -666,6 +666,7 @@ enabled: run `brigade extras on` once, or set `BRIGADE_EXTRAS=1`.
666666
- `brigade work plan-proposals`
667667
- `brigade work plans`
668668
- `brigade work recap`
669+
- `brigade work resolve-target`
669670
- `brigade work resume`
670671
- `brigade work review closeout`
671672
- `brigade work review finding-show`

src/brigade/claude_hooks/runtime.py

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from typing import Any, Iterator
1414

1515
from .. import localio
16-
from ..config import load_config
16+
from ..wiring import resolve_wired_target
1717
from .package import PACKAGE_REF
1818

1919
BRIEF_TIMEOUT_SECONDS = 10
@@ -116,28 +116,6 @@ def iter_session_states(
116116
yield state
117117

118118

119-
def resolve_wired_target(cwd: object) -> Path | None:
120-
if not isinstance(cwd, str) or not cwd.strip():
121-
return None
122-
try:
123-
current = Path(cwd).expanduser().resolve()
124-
except OSError:
125-
return None
126-
if not current.is_dir():
127-
current = current.parent
128-
for candidate in (current, *current.parents):
129-
if not (candidate / ".brigade" / "config.json").is_file():
130-
continue
131-
try:
132-
config = load_config(candidate)
133-
except (OSError, ValueError, json.JSONDecodeError):
134-
return None
135-
if config is not None and "claude" in config.selection.harnesses:
136-
return candidate
137-
return None
138-
return None
139-
140-
141119
def _advance_quote_state(text: str, quote: str | None) -> str | None:
142120
index = 0
143121
while index < len(text):

src/brigade/cli/work/dispatching.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,22 @@ def dispatch(args) -> int:
4040
return work_cmd.resume(target=args.target)
4141
if args.work_command == "brief":
4242
return work_cmd.brief(target=args.target, limit=args.limit, json_output=args.json)
43+
if args.work_command == "resolve-target":
44+
from ...wiring import resolve_wired_target
45+
46+
harness = args.harness.strip() if isinstance(args.harness, str) and args.harness.strip() else None
47+
try:
48+
cwd = str(args.cwd.expanduser().resolve())
49+
except OSError:
50+
cwd = str(args.cwd)
51+
target = resolve_wired_target(cwd, harness=harness)
52+
if args.json:
53+
print(json.dumps({"ok": target is not None, "target": str(target) if target else None}, indent=2))
54+
return 0 if target is not None else 1
55+
if target is None:
56+
return 1
57+
print(target)
58+
return 0
4359
if args.work_command == "hooks":
4460
from ...claude_hooks import install_cmd
4561

src/brigade/cli/work/registration.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,22 @@ def register(sub: argparse._SubParsersAction) -> None:
4747
p_work_brief.add_argument("--target", "-t", type=Path, default=Path("."), help="Repo or workspace to inspect.")
4848
p_work_brief.add_argument("--limit", type=int, default=3, help="Maximum recent sessions to include.")
4949
p_work_brief.add_argument("--json", action="store_true", help="Print machine-readable JSON.")
50+
p_work_resolve_target = work_sub.add_parser(
51+
"resolve-target",
52+
help="Print the Brigade-wired project root for a cwd (requires .brigade/config.json).",
53+
)
54+
p_work_resolve_target.add_argument(
55+
"--cwd",
56+
type=Path,
57+
default=Path("."),
58+
help="Starting directory to walk upward from.",
59+
)
60+
p_work_resolve_target.add_argument(
61+
"--harness",
62+
default=None,
63+
help="Require this harness in .brigade/config.json (default: any wired project).",
64+
)
65+
p_work_resolve_target.add_argument("--json", action="store_true", help="Print machine-readable JSON.")
5066
p_work_hooks = work_sub.add_parser("hooks", help="Manage project-scoped Claude work-loop hooks.")
5167
hooks_sub = p_work_hooks.add_subparsers(dest="hooks_command", metavar="<hooks-command>")
5268
hooks_sub.required = True
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
{
2+
"hooks": {
3+
"SessionStart": [
4+
{
5+
"hooks": [
6+
{
7+
"type": "command",
8+
"command": "REPLACE_WITH_HOOK_SCRIPT",
9+
"timeout": 5
10+
}
11+
]
12+
}
13+
],
14+
"PreToolUse": [
15+
{
16+
"matcher": "Bash|run_terminal_command|Edit|Write|MultiEdit|search_replace|write_file|apply_patch",
17+
"hooks": [
18+
{
19+
"type": "command",
20+
"command": "REPLACE_WITH_HOOK_SCRIPT",
21+
"timeout": 5
22+
}
23+
]
24+
}
25+
],
26+
"PostToolUse": [
27+
{
28+
"matcher": "Bash|run_terminal_command|Edit|Write|MultiEdit|search_replace|write_file|apply_patch",
29+
"hooks": [
30+
{
31+
"type": "command",
32+
"command": "REPLACE_WITH_HOOK_SCRIPT",
33+
"timeout": 5
34+
}
35+
]
36+
}
37+
],
38+
"Stop": [
39+
{
40+
"hooks": [
41+
{
42+
"type": "command",
43+
"command": "REPLACE_WITH_HOOK_SCRIPT",
44+
"timeout": 30
45+
}
46+
]
47+
}
48+
],
49+
"SessionEnd": [
50+
{
51+
"hooks": [
52+
{
53+
"type": "command",
54+
"command": "REPLACE_WITH_HOOK_SCRIPT",
55+
"timeout": 30
56+
}
57+
]
58+
}
59+
]
60+
}
61+
}

0 commit comments

Comments
 (0)