Skip to content

Headless Server Mode#181

Open
OBenner wants to merge 13 commits intodevelopfrom
auto-claude/202-headless-server-mode
Open

Headless Server Mode#181
OBenner wants to merge 13 commits intodevelopfrom
auto-claude/202-headless-server-mode

Conversation

@OBenner
Copy link
Copy Markdown
Owner

@OBenner OBenner commented Mar 23, 2026

Run Auto Code as a background server with remote agent execution, accessible via API, CLI, or web UI for server and CI/CD environments.

Test User and others added 13 commits March 23, 2026 13:34
- Add DaemonManager class with write_pid/read_pid/remove_pid/is_running
- Register SIGTERM, SIGINT (and SIGHUP on POSIX) for graceful shutdown
- Support optional shutdown callback (sync and async)
- Context manager support for automatic PID cleanup
- Integrate daemon lifecycle into main.py lifespan via HEADLESS env var

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ent tasks

- Added `get_graceful_shutdown_handler` and `_cancel_all_running_tasks` to agent_runner.py
- `_cancel_all_running_tasks` cancels all active asyncio tasks, awaits completion, and logs results
- Updated main.py lifespan to await the shutdown handler before removing PID file
- Ensures no agent tasks are left dangling when the server stops on SIGTERM

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Creates server_commands.py with handle_server_command dispatcher
and individual handlers for managing the Auto Code web-backend
daemon. Integrates with DaemonManager PID file conventions.
Updates main.py with --server, --server-host, --server-port, and
--server-log-lines arguments wired into the CLI dispatch loop.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ackend

- Add auto-claude-server.service systemd unit file with security hardening
  (NoNewPrivileges, PrivateTmp, ProtectSystem, ProtectHome)
- Add auto-claude-server.conf environment configuration template with
  documented settings for server, security, auth, logging, and agent config
- Add deploy/README.md with quick-install guide, service management commands,
  configuration reference, and troubleshooting tips

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Creates metrics_collector.py with Prometheus counters for agent runs,
errors, and latency histograms. Gracefully degrades when prometheus-client
is not installed. Adds prometheus-client>=0.20.0 to requirements.txt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ints

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Creates api/routes/metrics.py exposing application metrics in Prometheus
text format. Includes uptime gauge, HTTP request counter/histogram, and
agent task gauges. Registers router in main.py.
…runner

- Add user_id parameter to start_agent_task and run_agent_async
- Check per-user quota via ResourceManager.acquire_agent_slot before launching task
- Raise QuotaExceededError when concurrent agent or daily run limit exceeded
- Release agent slot in run_agent_async finally block on completion or failure
- Update agents route to extract user_id from JWT sub claim
- Handle QuotaExceededError as HTTP 429 in run_agent endpoint

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ectories

When user_id > 0, resolve specs from .auto-claude/users/{user_id}/specs/
instead of the shared .auto-claude/specs/ directory, providing workspace
isolation between users in multi-tenant deployments.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Added _JsonFormatter class to main.py emitting structured JSON log records
- Added _configure_logging() helper selecting JSON or text formatter via LOG_FORMAT env var
- Added LOG_LEVEL and LOG_FORMAT settings to core/config.py Settings class

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add HEADLESS_MODE, MAX_CONCURRENT_AGENTS, and AGENT_TIMEOUT_SECONDS
to Settings class and .env.example for headless server operation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…gnatures

- Add test_health_metrics_e2e.py with 24 tests covering /health/live,
  /health/ready, /health/detailed, and /metrics endpoints
- Fix pre-existing test_api_agents.py failures caused by subtask-3-2
  adding user_id param to start_agent_task (update 7 mock signatures)
- All 193 tests pass (excluding cloud e2e integration tests)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
3 Security Hotspots

See analysis details on SonarQube Cloud

# Stale PID file
try:
pid_file.unlink()
except OSError:

Check notice

Code scanning / CodeQL

Empty except Note

'except' clause does nothing but pass and there is no explanatory comment.

Copilot Autofix

AI 22 days ago

In general, to fix an “empty except” you either (a) add an explanatory comment stating clearly why the exception is being ignored, or (b) perform some minimal handling such as logging, or (c) narrow the exception type and re-raise or propagate as appropriate. Here, we don’t want to change _is_running’s behavior—the function should still return False if the process doesn’t exist, regardless of whether PID file cleanup succeeds. The safest fix is to add explicit handling while keeping the return value the same.

The single best approach here is to keep the except OSError block but replace pass with either a short comment and/or a debug log. Since we’re told not to assume logging infrastructure beyond what’s visible in the snippet, and to avoid changing imports aside from well-known libraries, the minimal, non-invasive change is to add a comment explaining that failure to delete the PID file is intentionally ignored. This satisfies CodeQL’s requirement (the “does nothing but pass and there is no explanatory comment” part) without altering behavior.

Concretely, in apps/backend/cli/server_commands.py, inside _is_running, change the except OSError: block that currently just passes so that it includes an explanatory comment before pass, e.g. # Best-effort cleanup; ignore errors when removing stale PID file. No new imports or method definitions are needed.

Suggested changeset 1
apps/backend/cli/server_commands.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/apps/backend/cli/server_commands.py b/apps/backend/cli/server_commands.py
--- a/apps/backend/cli/server_commands.py
+++ b/apps/backend/cli/server_commands.py
@@ -63,6 +63,7 @@
         try:
             pid_file.unlink()
         except OSError:
+            # Best-effort cleanup: ignore errors when removing a stale PID file.
             pass
         return False
     except PermissionError:
EOF
@@ -63,6 +63,7 @@
try:
pid_file.unlink()
except OSError:
# Best-effort cleanup: ignore errors when removing a stale PID file.
pass
return False
except PermissionError:
Copilot is powered by AI and may make mistakes. Always verify output.
print_status(f"Process {pid} not found; cleaning up PID file", "warning")
try:
pid_file.unlink()
except OSError:

Check notice

Code scanning / CodeQL

Empty except Note

'except' clause does nothing but pass and there is no explanatory comment.

Copilot Autofix

AI 22 days ago

In general, empty except blocks should either (a) be removed so the exception can propagate, or (b) explicitly document and/or log why the exception is being ignored and, if appropriate, perform compensating actions. The goal is to avoid silently discarding information about errors.

For this specific case in handle_server_stop_command (apps/backend/cli/server_commands.py, around lines 188–191), we want to keep the “best-effort cleanup” behavior—i.e., a failure to delete the PID file should not cause the stop command itself to fail—but we should not ignore the exception silently. The most consistent fix with the existing code is:

  • Catch OSError as exc.
  • Use the already-imported print_status helper to log a warning that we could not remove the PID file, including the exception message for debugging.
  • Preserve the existing control flow: after handling the OSError, still return True from the outer except ProcessLookupError block.

No new imports or helper methods are needed; print_status is already available and used for status, warning, and error messages elsewhere in the file.

Suggested changeset 1
apps/backend/cli/server_commands.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/apps/backend/cli/server_commands.py b/apps/backend/cli/server_commands.py
--- a/apps/backend/cli/server_commands.py
+++ b/apps/backend/cli/server_commands.py
@@ -187,8 +187,11 @@
         print_status(f"Process {pid} not found; cleaning up PID file", "warning")
         try:
             pid_file.unlink()
-        except OSError:
-            pass
+        except OSError as exc:
+            print_status(
+                f"Failed to remove PID file {pid_file}: {exc}",
+                "warning",
+            )
         return True
     except PermissionError:
         print_status(
EOF
@@ -187,8 +187,11 @@
print_status(f"Process {pid} not found; cleaning up PID file", "warning")
try:
pid_file.unlink()
except OSError:
pass
except OSError as exc:
print_status(
f"Failed to remove PID file {pid_file}: {exc}",
"warning",
)
return True
except PermissionError:
print_status(
Copilot is powered by AI and may make mistakes. Always verify output.
import logging
import os
import signal
import sys

Check notice

Code scanning / CodeQL

Unused import Note

Import of 'sys' is not used.

Copilot Autofix

AI 22 days ago

The way to fix an unused import is to remove the unnecessary import statement, as it adds clutter and an unneeded dependency. In this file, only the sys module is reported as unused; other imports are clearly referenced in the shown code.

Concretely, in apps/web-backend/core/daemon.py, at the top of the file, remove the line import sys (line 12 in the snippet). No other code changes, new methods, or additional imports are required, since nothing in the provided code uses sys. This preserves all existing functionality while resolving the CodeQL warning.

Suggested changeset 1
apps/web-backend/core/daemon.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/apps/web-backend/core/daemon.py b/apps/web-backend/core/daemon.py
--- a/apps/web-backend/core/daemon.py
+++ b/apps/web-backend/core/daemon.py
@@ -9,7 +9,6 @@
 import logging
 import os
 import signal
-import sys
 from pathlib import Path
 from typing import Callable, Optional
 
EOF
@@ -9,7 +9,6 @@
import logging
import os
import signal
import sys
from pathlib import Path
from typing import Callable, Optional

Copilot is powered by AI and may make mistakes. Always verify output.

if _daemon is not None:
_daemon.remove_pid()
_daemon = None

Check notice

Code scanning / CodeQL

Unused global variable Note

The global variable '_daemon' is not used.

Copilot Autofix

AI 22 days ago

In general, to fix an "unused global variable" warning, either (a) remove the global variable and keep only the side effects of its initialization, (b) rename it to an accepted "intentionally unused" pattern if it truly must remain unused, or (c) avoid using global and keep the variable local if it is only needed within a single function.

Here, _daemon is only used inside lifespan and does not need to be global. The best fix without changing functionality is:

  • Remove the global _daemon statement.
  • Ensure _daemon is always defined in lifespan before it is checked, by initializing it to None at the top of the function body.
  • Leave the existing uses of _daemon in HEADLESS mode and the shutdown block unchanged.

Concretely, in apps/web-backend/main.py, in the lifespan function around lines 79–118:

  • Delete the global _daemon # noqa: PLW0603 line.
  • Immediately after the docstring line ("""Application lifespan handler..."""), add _daemon = None with the same indentation as other local variables in the function.

No extra imports or helper methods are needed.

Suggested changeset 1
apps/web-backend/main.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/apps/web-backend/main.py b/apps/web-backend/main.py
--- a/apps/web-backend/main.py
+++ b/apps/web-backend/main.py
@@ -80,7 +80,7 @@
     """
     Application lifespan handler for startup and shutdown events
     """
-    global _daemon  # noqa: PLW0603
+    _daemon = None
 
     # Startup
     logger.info("Starting Web Backend API")
EOF
@@ -80,7 +80,7 @@
"""
Application lifespan handler for startup and shutdown events
"""
global _daemon # noqa: PLW0603
_daemon = None

# Startup
logger.info("Starting Web Backend API")
Copilot is powered by AI and may make mistakes. Always verify output.
are registered, respond correctly, and return expected formats.
"""

import pytest

Check notice

Code scanning / CodeQL

Unused import Note test

Import of 'pytest' is not used.

Copilot Autofix

AI 22 days ago

In general, to fix an unused-import issue you remove the import statement when the imported name is not referenced anywhere in the file, provided the framework or runtime does not require that import implicitly.

Here, the single best fix is to delete the line import pytest at the top of apps/web-backend/tests/test_health_metrics_e2e.py. No other code changes are necessary, because:

  • All tests are plain functions or methods named test_*, which pytest will discover without needing an explicit pytest import.
  • No pytest APIs (e.g., pytest.mark, pytest.raises) are used in the shown code.
    This change preserves all existing behavior while cleaning up an unnecessary dependency.

Concretely, in apps/web-backend/tests/test_health_metrics_e2e.py, remove line 8 containing import pytest and leave the from fastapi.testclient import TestClient import intact.

Suggested changeset 1
apps/web-backend/tests/test_health_metrics_e2e.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/apps/web-backend/tests/test_health_metrics_e2e.py b/apps/web-backend/tests/test_health_metrics_e2e.py
--- a/apps/web-backend/tests/test_health_metrics_e2e.py
+++ b/apps/web-backend/tests/test_health_metrics_e2e.py
@@ -5,7 +5,6 @@
 are registered, respond correctly, and return expected formats.
 """
 
-import pytest
 from fastapi.testclient import TestClient
 
 
EOF
@@ -5,7 +5,6 @@
are registered, respond correctly, and return expected formats.
"""

import pytest
from fastapi.testclient import TestClient


Copilot is powered by AI and may make mistakes. Always verify output.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 23, 2026

Warning

Rate limit exceeded

@OBenner has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 10 minutes and 42 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 248c2903-c41b-4160-895a-dcef96924c25

📥 Commits

Reviewing files that changed from the base of the PR and between 5e88031 and 2357f12.

📒 Files selected for processing (18)
  • apps/backend/cli/main.py
  • apps/backend/cli/server_commands.py
  • apps/web-backend/.env.example
  • apps/web-backend/api/routes/agents.py
  • apps/web-backend/api/routes/health.py
  • apps/web-backend/api/routes/metrics.py
  • apps/web-backend/core/config.py
  • apps/web-backend/core/daemon.py
  • apps/web-backend/deploy/README.md
  • apps/web-backend/deploy/auto-claude-server.conf
  • apps/web-backend/deploy/auto-claude-server.service
  • apps/web-backend/main.py
  • apps/web-backend/requirements.txt
  • apps/web-backend/services/agent_runner.py
  • apps/web-backend/services/metrics_collector.py
  • apps/web-backend/services/resource_manager.py
  • apps/web-backend/tests/test_api_agents.py
  • apps/web-backend/tests/test_health_metrics_e2e.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch auto-claude/202-headless-server-mode

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants