@@ -1005,7 +1005,7 @@ fn assert_cursor_rendered_bundle_valid(plugin_dir: &Path, bin: &str) {
10051005}
10061006
10071007/// Full structural validation of a rendered Codex plugin bundle.
1008- fn assert_codex_rendered_bundle_valid ( plugin_dir : & Path , bin : & str ) {
1008+ fn assert_codex_rendered_bundle_valid ( plugin_dir : & Path , bin : & str , expect_hooks : bool ) {
10091009 // Rendered manifest: version stamped to this binary's package version.
10101010 let manifest = read_json ( & plugin_dir. join ( ".codex-plugin/plugin.json" ) ) ;
10111011 assert_eq ! ( manifest[ "name" ] , "tracedecay" ) ;
@@ -1015,10 +1015,25 @@ fn assert_codex_rendered_bundle_valid(plugin_dir: &Path, bin: &str) {
10151015 "rendered manifest version must match the binary's package version"
10161016 ) ;
10171017
1018- // Rendered hooks.json: Codex nests handlers in matcher groups; every
1019- // handler command is the quoted absolute binary plus a hook-codex-*
1020- // subcommand.
1021- let hooks = read_json ( & plugin_dir. join ( "hooks/hooks.json" ) ) ;
1018+ // Rendered hooks.json: only personal/global bundles ship lifecycle hooks;
1019+ // repo-local bundles omit them (Codex only trusts personal-plugin hooks).
1020+ let hooks_path = plugin_dir. join ( "hooks/hooks.json" ) ;
1021+ if !expect_hooks {
1022+ assert ! (
1023+ !hooks_path. exists( ) ,
1024+ "repo-local Codex plugin must not ship {}" ,
1025+ hooks_path. display( )
1026+ ) ;
1027+ let manifest = read_json ( & plugin_dir. join ( ".codex-plugin/plugin.json" ) ) ;
1028+ assert ! (
1029+ manifest. get( "hooks" ) . is_none( ) ,
1030+ "repo-local Codex plugin manifest must not declare lifecycle hooks"
1031+ ) ;
1032+ return ;
1033+ }
1034+ // Codex nests handlers in matcher groups; every handler command is the
1035+ // quoted absolute binary plus a hook-codex-* subcommand.
1036+ let hooks = read_json ( & hooks_path) ;
10221037 let events = hooks[ "hooks" ]
10231038 . as_object ( )
10241039 . expect ( "rendered hooks.json must contain a hooks object" ) ;
@@ -1088,7 +1103,7 @@ fn codex_install_renders_structurally_valid_bundle() {
10881103 codex. install ( & ctx ( home. path ( ) , NEW_BIN ) ) . unwrap ( ) ;
10891104
10901105 let plugin_dir = codex_bootstrap_dir ( home. path ( ) ) ;
1091- assert_codex_rendered_bundle_valid ( & plugin_dir, NEW_BIN ) ;
1106+ assert_codex_rendered_bundle_valid ( & plugin_dir, NEW_BIN , true ) ;
10921107
10931108 // Global-scope MCP rendering: absolute command, plain `serve` args, and
10941109 // the global-DB env flag.
@@ -1110,7 +1125,7 @@ fn codex_local_install_renders_project_scoped_mcp() {
11101125 . unwrap ( ) ;
11111126
11121127 let plugin_dir = codex_bootstrap_dir ( project. path ( ) ) ;
1113- assert_codex_rendered_bundle_valid ( & plugin_dir, NEW_BIN ) ;
1128+ assert_codex_rendered_bundle_valid ( & plugin_dir, NEW_BIN , false ) ;
11141129
11151130 // Project-local scope renders relative-path serve args and drops the
11161131 // global-DB env flag.
0 commit comments