Improve coding agent reliability
Some checks failed
CI / test (push) Has been cancelled

This commit is contained in:
2026-08-21 23:46:16 +02:00
parent ed7b88ade4
commit 2df37b671b
11 changed files with 31 additions and 8 deletions

View File

@@ -17,7 +17,7 @@ AUTOMATION_MODE=dry-run
ALLOW_DIRECT_MAIN=false ALLOW_DIRECT_MAIN=false
AGENT_COMMAND=aider AGENT_COMMAND=aider
AGENT_MODEL=mistral/mistral-medium-latest AGENT_MODEL=mistral/devstral-latest
MISTRAL_API_KEY= MISTRAL_API_KEY=
POLL_SECONDS=60 POLL_SECONDS=60

View File

@@ -88,7 +88,7 @@ die empfohlene Betriebsart.
Default: Default:
```dotenv ```dotenv
AGENT_MODEL=mistral/mistral-medium-latest AGENT_MODEL=mistral/devstral-latest
``` ```
Das Chat-Modell und Coding-Modell sind bewusst getrennt. Fuer Coding-Agenten sollte ein Das Chat-Modell und Coding-Modell sind bewusst getrennt. Fuer Coding-Agenten sollte ein

View File

@@ -18,7 +18,7 @@ services:
GITEA_USERNAME: ${GITEA_USERNAME:-automation-bot} GITEA_USERNAME: ${GITEA_USERNAME:-automation-bot}
GITEA_TOKEN: ${GITEA_TOKEN:-} GITEA_TOKEN: ${GITEA_TOKEN:-}
AGENT_COMMAND: ${AGENT_COMMAND:-aider} AGENT_COMMAND: ${AGENT_COMMAND:-aider}
AGENT_MODEL: ${AGENT_MODEL:-mistral/mistral-medium-latest} AGENT_MODEL: ${AGENT_MODEL:-mistral/devstral-latest}
MISTRAL_API_KEY: ${MISTRAL_API_KEY:-} MISTRAL_API_KEY: ${MISTRAL_API_KEY:-}
POLL_SECONDS: ${POLL_SECONDS:-60} POLL_SECONDS: ${POLL_SECONDS:-60}
STALE_CLAIM_MINUTES: ${STALE_CLAIM_MINUTES:-90} STALE_CLAIM_MINUTES: ${STALE_CLAIM_MINUTES:-90}

View File

@@ -2,6 +2,7 @@ from __future__ import annotations
import os import os
import pwd import pwd
import shutil
import subprocess import subprocess
from pathlib import Path from pathlib import Path
@@ -18,6 +19,13 @@ class AiderAgent:
task_file.chmod(0o600) task_file.chmod(0o600)
command = self.command(task_file) command = self.command(task_file)
coder = pwd.getpwnam("coder") coder = pwd.getpwnam("coder")
git_config = Path(coder.pw_dir) / ".gitconfig"
git_config.write_text(
"[user]\n\tname = LAI Automation Coder\n\temail = coder@localhost\n"
f"[safe]\n\tdirectory = {workspace.resolve()}\n"
)
git_config.chmod(0o600)
os.chown(git_config, coder.pw_uid, coder.pw_gid)
os.chown(task_file, coder.pw_uid, coder.pw_gid) os.chown(task_file, coder.pw_uid, coder.pw_gid)
for path in workspace.rglob("*"): for path in workspace.rglob("*"):
if ".git" not in path.parts: if ".git" not in path.parts:
@@ -49,6 +57,9 @@ class AiderAgent:
timeout=900, timeout=900,
) )
finally: finally:
shutil.rmtree(workspace / ".aider.tags.cache.v4", ignore_errors=True)
for artifact in (".aider.chat.history.md", ".aider.input.history"):
(workspace / artifact).unlink(missing_ok=True)
os.chown(workspace, 0, coder.pw_gid, follow_symlinks=False) os.chown(workspace, 0, coder.pw_gid, follow_symlinks=False)
workspace.chmod(0o775) workspace.chmod(0o775)
if result.returncode != 0: if result.returncode != 0:
@@ -72,4 +83,8 @@ class AiderAgent:
"--input-history-file", "--input-history-file",
"/dev/null", "/dev/null",
"--no-analytics", "--no-analytics",
"--edit-format",
"diff",
"--timeout",
"60",
] ]

View File

@@ -64,7 +64,7 @@ class Settings:
gitea_token=os.getenv("GITEA_TOKEN", ""), gitea_token=os.getenv("GITEA_TOKEN", ""),
gitea_username=os.getenv("GITEA_USERNAME", "automation-bot"), gitea_username=os.getenv("GITEA_USERNAME", "automation-bot"),
agent_command=os.getenv("AGENT_COMMAND", "aider"), agent_command=os.getenv("AGENT_COMMAND", "aider"),
agent_model=os.getenv("AGENT_MODEL", "mistral/mistral-medium-latest"), agent_model=os.getenv("AGENT_MODEL", "mistral/devstral-latest"),
test_commands=_commands( test_commands=_commands(
os.getenv( os.getenv(
"TEST_COMMANDS", "TEST_COMMANDS",

View File

@@ -15,6 +15,7 @@ _ALWAYS_DENIED = (
"docker-compose", "docker-compose",
".gitea/", ".gitea/",
".github/", ".github/",
".aider",
) )
_DIRECT_MAIN_DENIED = ( _DIRECT_MAIN_DENIED = (

View File

@@ -36,6 +36,7 @@ def build_task(report: FailureReport) -> str:
Rules: Rules:
- First add a regression test that reproduces the reported behavior. - First add a regression test that reproduces the reported behavior.
- The regression test must execute the relevant production behavior; never mock or stub the behavior being tested.
- Make the smallest production change that fixes the root cause. - Make the smallest production change that fixes the root cause.
- Preserve read-only SQL validation and tenant isolation. - Preserve read-only SQL validation and tenant isolation.
- Never read or modify .env files, credentials, deployment secrets, or production data. - Never read or modify .env files, credentials, deployment secrets, or production data.

View File

@@ -108,7 +108,8 @@ class AutomationWorker:
"stderr": result.stderr[-6000:], "stderr": result.stderr[-6000:],
}) })
if result.returncode != 0: if result.returncode != 0:
raise RuntimeError(f"Test command failed: {command}\n{result.stderr[-2000:]}") output = (result.stdout + "\n" + result.stderr)[-4000:]
raise RuntimeError(f"Test command failed: {command}\n{output}")
return results return results
@staticmethod @staticmethod

View File

@@ -12,3 +12,4 @@ def test_aider_command_cannot_mutate_git_configuration_or_send_analytics() -> No
assert "--no-analytics" in command assert "--no-analytics" in command
assert "--no-auto-commits" in command assert "--no-auto-commits" in command
assert command.count("/dev/null") == 2 assert command.count("/dev/null") == 2
assert command[-4:] == ["--edit-format", "diff", "--timeout", "60"]

View File

@@ -15,9 +15,12 @@ def test_policy_accepts_small_application_change() -> None:
def test_policy_rejects_secrets_and_vendor_paths() -> None: def test_policy_rejects_secrets_and_vendor_paths() -> None:
result = evaluate_changes(ChangeSummary((".env", "vendor/foo.php"), 2, 0), settings()) result = evaluate_changes(
ChangeSummary((".env", "vendor/foo.php", ".aider.tags.cache.v4/cache.db"), 3, 0),
settings(),
)
assert not result.allowed assert not result.allowed
assert len(result.errors) == 2 assert len(result.errors) == 3
def test_direct_main_blocks_validator_and_migrations() -> None: def test_direct_main_blocks_validator_and_migrations() -> None:

View File

@@ -30,6 +30,7 @@ def test_task_contains_constraints_and_redacted_report() -> None:
assert "secret-value" not in task assert "secret-value" not in task
assert "admin@example.com" not in task assert "admin@example.com" not in task
assert "Do not commit or push" in task assert "Do not commit or push" in task
assert "never mock or stub the behavior being tested" in task
def test_redact_removes_extended_credentials() -> None: def test_redact_removes_extended_credentials() -> None: