Skip to content

Commit f114350

Browse files
Merge pull request #2537 from gitautoai/wes
Fix Python 3.14 event loop crash and tool dispatch for None args
2 parents 3aae7f6 + cf90314 commit f114350

7 files changed

Lines changed: 91 additions & 8 deletions

File tree

.claude/hooks/no-skip-hooks.sh

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#!/bin/bash
2+
# Bash hook: Block --no-verify on git commit
3+
INPUT=$(cat)
4+
CMD=$(echo "$INPUT" | jq -r '.tool_input.command // ""')
5+
6+
# Extract only the git commit portion (before any && or ; chain), then strip the message
7+
COMMIT_CMD=$(echo "$CMD" | grep -oE '(^|&&|;)\s*git commit[^&;]*' | head -1)
8+
if [ -n "$COMMIT_CMD" ]; then
9+
# Strip commit message to avoid false positives
10+
FLAGS=$(echo "$COMMIT_CMD" | sed 's/ -m .*//; s/ -m".*//; s/ -m$//')
11+
if echo "$FLAGS" | grep -q -- "--no-verify"; then
12+
jq -n '{
13+
"decision": "block",
14+
"reason": "BLOCKED: --no-verify is not allowed. Commit without it."
15+
}'
16+
exit 2
17+
fi
18+
fi
19+
20+
exit 0

.claude/settings.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
{
22
"hooks": {
3+
"PreToolUse": [
4+
{
5+
"matcher": "Bash",
6+
"hooks": [
7+
{
8+
"type": "command",
9+
"command": "/Users/rwest/Repositories/gitauto/.claude/hooks/no-skip-hooks.sh",
10+
"timeout": 5
11+
}
12+
]
13+
}
14+
],
315
"Stop": [
416
{
517
"matcher": "",

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "GitAuto"
3-
version = "1.1.9"
3+
version = "1.1.12"
44
requires-python = ">=3.14"
55
dependencies = [
66
"annotated-doc==0.0.4",

scripts/lint/check_test_files.sh

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,19 +38,15 @@ if [ -n "$STAGED_IMPL_NEW" ]; then
3838
fi
3939

4040
# Check 2: Changed impl files with existing test files must have test also staged
41-
# Exception: if the test file has no unstaged changes (identical to HEAD), it's fine
4241
if [ -n "$STAGED_IMPL_MODIFIED" ]; then
4342
for file in $STAGED_IMPL_MODIFIED; do
4443
dir=$(dirname "$file")
4544
base=$(basename "$file")
4645
test_file="$dir/test_$base"
4746
if [ -f "$test_file" ]; then
4847
if ! echo "$STAGED_ALL" | grep -qF "$test_file"; then
49-
# Allow if test file is unchanged from HEAD (e.g. import-only refactors)
50-
if ! git diff --quiet HEAD -- "$test_file" 2>/dev/null; then
51-
echo "TEST NOT UPDATED: $file changed but $test_file is not staged"
52-
FAIL=1
53-
fi
48+
echo "TEST NOT STAGED: $file changed but $test_file is not staged"
49+
FAIL=1
5450
fi
5551
fi
5652
done

services/chat_with_agent.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,11 @@ async def chat_with_agent(
246246
tool_result = tools_to_call[tool_name](
247247
**tool_args, base_args=base_args, messages=messages
248248
)
249+
else:
250+
# Model passed None or non-dict args (e.g. verify_task_is_complete with no args)
251+
tool_result = tools_to_call[tool_name](
252+
base_args=base_args, messages=messages
253+
)
249254
if inspect.iscoroutine(tool_result):
250255
tool_result = await tool_result
251256

services/test_chat_with_agent.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,56 @@ async def test_verify_task_is_complete_without_pr_changes_returns_is_completed_f
444444
assert "No changes were needed" in last_message
445445

446446

447+
@pytest.mark.asyncio
448+
@patch("services.chat_with_agent.chat_with_model")
449+
@patch("services.agents.verify_task_is_complete.get_pull_request_files")
450+
@patch("services.chat_with_agent.update_comment")
451+
async def test_verify_task_is_complete_with_none_args_still_executes(
452+
_mock_update_comment, mock_get_pr_files, mock_chat_with_model, create_test_base_args
453+
):
454+
"""PR 786: Gemma 4 31B called verify_task_is_complete with args=None instead of {}.
455+
isinstance(None, dict) is False, so the tool was silently skipped and returned None.
456+
Gemma then entered a dead loop returning empty responses for 20 iterations."""
457+
mock_get_pr_files.return_value = [{"filename": "test.py", "status": "modified"}]
458+
mock_chat_with_model.return_value = (
459+
{
460+
"role": "assistant",
461+
"content": [
462+
{
463+
"type": "tool_use",
464+
"id": "test_id",
465+
"name": "verify_task_is_complete",
466+
"input": {},
467+
}
468+
],
469+
},
470+
[ToolCall(id="test_id", name="verify_task_is_complete", args=None)],
471+
15,
472+
10,
473+
)
474+
475+
base_args = create_test_base_args(
476+
model_id=GoogleModelId.GEMMA_4_31B,
477+
owner="test-owner",
478+
repo="test-repo",
479+
pr_number=123,
480+
token="test-token",
481+
)
482+
483+
result = await chat_with_agent(
484+
messages=[{"role": "user", "content": "test"}],
485+
system_message="test system message",
486+
base_args=base_args,
487+
tools=[],
488+
usage_id=123,
489+
model_id=GoogleModelId.GEMMA_4_31B,
490+
)
491+
492+
# verify_task_is_complete was called (not skipped), so is_completed should be True
493+
assert result.is_completed is True
494+
mock_get_pr_files.assert_called_once()
495+
496+
447497
@pytest.mark.asyncio
448498
@patch("services.chat_with_agent.chat_with_model")
449499
async def test_regular_tool_returns_is_completed_false(

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)