Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,11 @@ class GitHubCopilotOptions(TypedDict, total=False):
timeout: float
"""Request timeout in seconds. Defaults to GITHUB_COPILOT_TIMEOUT environment variable or 60 seconds."""

enable_file_hooks: bool
"""Whether the session reads checkout-local ``.github/hooks/`` files. Defaults to
False so behavior does not depend on the working directory the agent runs in;
set True (via default_options or per-run options) to opt into checkout-driven hooks."""

log_level: str
"""CLI log level. Defaults to GITHUB_COPILOT_LOG_LEVEL environment variable."""

Expand Down Expand Up @@ -1232,6 +1237,11 @@ def _build_session_kwargs(
)
kwargs["hooks"] = self._build_session_hooks(all_tools, kwargs)

# The SDK's default reads `.github/hooks/` from the process working
# directory, so identical agents would behave differently depending on
# where they were started. Pin it off unless the caller opts in.
kwargs.setdefault("enable_file_hooks", False)

# Strip agent-internal and client-level keys that are consumed here or in the
# run methods (and settings) but are NOT valid create_session parameters, so
# they don't leak through the passthrough layer and raise TypeError.
Expand Down
31 changes: 31 additions & 0 deletions python/packages/github_copilot/tests/test_github_copilot_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -1125,6 +1125,7 @@ async def test_session_resumed_for_same_session(
model=unittest.mock.ANY,
on_permission_request=unittest.mock.ANY,
hooks=unittest.mock.ANY,
enable_file_hooks=unittest.mock.ANY,
)

async def test_session_config_includes_model(
Expand Down Expand Up @@ -1904,6 +1905,35 @@ def runtime_hook(_input: Any, _context: Any) -> Any:
# on_pre_tool_use is still honored via the hooks parameter.
assert config["hooks"]["on_pre_tool_use"] is runtime_hook

async def test_enable_file_hooks_defaults_to_false(
self,
mock_client: MagicMock,
) -> None:
"""Sessions must not depend on the cwd: file hooks default to off."""
agent = GitHubCopilotAgent(client=mock_client)
await agent.start()

await agent._get_or_create_session(AgentSession()) # type: ignore[reportPrivateUsage]

config = mock_client.create_session.call_args.kwargs
assert config["enable_file_hooks"] is False

async def test_enable_file_hooks_opt_in_wins(
self,
mock_client: MagicMock,
) -> None:
"""A caller who wants checkout-driven hooks can turn them back on."""
agent = GitHubCopilotAgent(
client=mock_client,
default_options=cast(Any, {"enable_file_hooks": True}),
)
await agent.start()

await agent._get_or_create_session(AgentSession()) # type: ignore[reportPrivateUsage]

config = mock_client.create_session.call_args.kwargs
assert config["enable_file_hooks"] is True


class TestGitHubCopilotAgentToolConversion:
"""Test cases for tool conversion."""
Expand Down Expand Up @@ -3667,3 +3697,4 @@ async def test_integration_run_with_shell_permissions_executes_command() -> None

if isinstance(session.service_session_id, str) and agent._client:
await agent._client.delete_session(session.service_session_id)

Loading