@@ -266,6 +266,44 @@ def check_gitignore_bypass(command: str) -> None:
266266 )
267267
268268
269+ def _extract_effective_cwd (command : str , default_cwd : str | None = None ) -> str | None :
270+ """Extract the effective working directory from a command string.
271+
272+ Detects two patterns:
273+ - ``cd <path> && ...`` or ``cd <path> ; ...`` prefix
274+ - ``git -C <path> ...`` flag
275+
276+ Returns the extracted path if found, otherwise default_cwd.
277+ """
278+ m = re .match (r'cd\s+(?:"([^"]+)"|(\S+))\s*(?:&&|;)' , command .lstrip ())
279+ if m :
280+ p = (m .group (1 ) or m .group (2 ) or "" ).strip ()
281+ if p :
282+ return p
283+ m = re .search (r'\bgit\s+-C\s+(?:"([^"]+)"|(\S+))' , command )
284+ if m :
285+ return m .group (1 ) or m .group (2 )
286+ return default_cwd
287+
288+
289+ def _is_worktree_on_feature_branch (cwd : str ) -> bool :
290+ """Return True if cwd is a worktree directory on a non-protected branch."""
291+ try :
292+ result = subprocess .run (
293+ ["git" , "branch" , "--show-current" ],
294+ capture_output = True ,
295+ text = True ,
296+ timeout = 5 ,
297+ cwd = cwd ,
298+ )
299+ if result .returncode == 0 :
300+ branch = result .stdout .strip ()
301+ return bool (branch ) and branch not in {"main" , "master" }
302+ except (subprocess .TimeoutExpired , OSError ):
303+ pass
304+ return False
305+
306+
269307def check_git_submission (command : str ) -> None :
270308 """Block raw git push, gh pr create, gh pr merge unless bypassed."""
271309 # Skills prefix blocked commands with CLAUDE_GATE_BYPASS=1 to pass through
@@ -274,6 +312,12 @@ def check_git_submission(command: str) -> None:
274312
275313 for pattern , skill_name , message in _GIT_SUBMISSION_PATTERNS :
276314 if pattern .search (command ):
315+ # Allow git push from worktree directories on feature branches
316+ if pattern is _GIT_SUBMISSION_PATTERNS [0 ][0 ]: # git push pattern
317+ effective_cwd = _extract_effective_cwd (command )
318+ project_dir = os .environ .get ("CLAUDE_PROJECT_DIR" , "" )
319+ if effective_cwd and effective_cwd != project_dir and _is_worktree_on_feature_branch (effective_cwd ):
320+ return
277321 _block (f"[git-submission-gate] BLOCKED: { message } \n [fix-with-skill] { skill_name } " )
278322
279323
0 commit comments