Remove legacy filesystem logs
Stop PrintStyle from creating per-process HTML files under /a0/logs and remove the obsolete tracked folder scaffolding. Delete existing legacy log directories during startup migration so self-updated and manually upgraded installations are cleaned automatically. Point plugin debugging guidance to Docker output and add regression coverage for repeatable cleanup.
Alessandro committed
Jul 16, 2026 at 15:30 UTC
8e5d643483fc550e01dc8ea76ffa6b4e7dbbdff4
12 files changed
+39
-60
.gitignore
-4
@@ -21,10 +21,6 @@
21
/knowledge/custom/
22
/instruments/
23
24
-# Handle logs directory
25
-logs/**
26
-!logs/**/
27
-
24
# Handle tmp and usr directory
25
tmp/**
26
!tmp/**/
AGENTS.md
-1
@@ -86,7 +86,6 @@ Intentionally unindexed local or generated roots:
86
| `.conda/`, `.venv/` | Local Python environments. |
87
| `.pytest_cache/`, `__pycache__/` | Generated test and bytecode caches. |
88
| `.vscode/`, `.windsurf/` | Editor-local configuration and assistant metadata. |
89
-| `logs/` | Runtime output. |
89
| `tmp/` | Ignored runtime caches, uploads, and generated work. |
90
| `usr/` | Ignored local user data, settings, plugins, chats, and workdirs. |
91
| `python/` | Generated or legacy runtime mirror; current source is in root modules and tracked source directories. |
docker/AGENTS.md
+1
-1
@@ -16,7 +16,7 @@
16
- Preserve the two-runtime model: the Python 3.12 framework runtime under `/opt/venv-a0` runs the WebUI, APIs, scheduler, framework imports, and plugin hooks; the Python 3.13 agent execution runtime under `/opt/venv` runs agent terminal tasks and user code.
17
- Verify backend imports and plugin hooks with `/opt/venv-a0`; packages installed into `/opt/venv` do not prove framework compatibility.
18
- Do not bake secrets, local `.env` values, or user data into images.
19
-- Keep compose mounts aligned with `usr/`, `logs/`, and other runtime-state expectations.
19
+- Keep compose mounts aligned with `usr/` and other runtime-state expectations.
20
- Image changes that affect GitHub publishing must stay synchronized with `.github/workflows/docker-publish.yml`.
21
22
## Work Guidance
docs/setup/vps-deployment.md
-1
@@ -708,7 +708,6 @@ curl -I https://your-domain.com/login
708
| Environment File | `/opt/a0-instance/.env` |
709
| Memory Storage | `/opt/a0-instance/memory/` |
710
| Work Directory | `/opt/a0-instance/work_dir/` |
711
-| Logs | `/opt/a0-instance/logs/` |
711
| Apache Config (Standard) | `/etc/apache2/sites-available/` |
712
| Apache Config (DirectAdmin) | `/etc/httpd/conf/extra/httpd-includes.conf` |
713
| DirectAdmin SSL Certs | `/usr/local/directadmin/data/users/USER/domains/` |
helpers/migration.py
+2
-1
@@ -130,7 +130,8 @@ def _cleanup_obsolete() -> None:
130
"""
131
to_remove = [
132
"knowledge/default",
133
- "memory"
133
+ "memory",
134
+ "logs",
135
]
136
for path in to_remove:
137
if files.exists(path):
helpers/migration.py.dox.md
+2
@@ -24,6 +24,7 @@
24
25
- Helper modules own reusable framework APIs and must preserve public callers unless all callers, tests, and docs are updated together.
26
- Update this file whenever public functions, classes, persistence behavior, path/security assumptions, side effects, or cross-module contracts change.
27
+- Startup cleanup removes obsolete `memory`, `knowledge/default`, and legacy `logs` directories; repeated runs are safe.
28
- Observed side-effect areas: filesystem reads, filesystem writes, filesystem deletion, settings/state persistence, secret handling, scheduler state.
29
- Imported dependency areas include: `helpers`, `helpers.print_style`, `json`, `os`.
30
@@ -42,6 +43,7 @@
43
44
- Run targeted tests for changed helper behavior; run security regressions for auth, filesystem, WebSocket, tunnel, upload, or secret-handling helpers.
45
- Related tests observed by source search:
46
+ - `tests/test_migration_cleanup.py`
47
- `tests/test_browser_agent_regressions.py`
48
- `tests/test_office_canvas_setup.py`
49
- `tests/test_office_document_store.py`
helpers/print_style.py
+7
-35
@@ -1,8 +1,10 @@
1
-import os, webcolors, html
1
+import html
2
import sys
3
-from datetime import datetime
3
from collections.abc import Mapping
5
-from . import files
4
+
5
+import webcolors
6
+
7
+from . import files # Load before strings; helpers.files imports sanitize_string.
8
from .strings import sanitize_string
9
10
_runtime_module = None
@@ -18,7 +20,6 @@ def _get_runtime():
20
21
class PrintStyle:
22
last_endline = True
21
- log_file_path = None
23
24
def __init__(self, bold=False, italic=False, underline=False, font_color="default", background_color="default", padding=False, log_only=False):
25
self.bold = bold
@@ -30,14 +31,6 @@ class PrintStyle:
31
self.padding_added = False # Flag to track if padding was added
32
self.log_only = log_only
33
33
- if PrintStyle.log_file_path is None:
34
- logs_dir = files.get_abs_path("logs")
35
- os.makedirs(logs_dir, exist_ok=True)
36
- log_filename = datetime.now().strftime("log_%Y%m%d_%H%M%S.html")
37
- PrintStyle.log_file_path = os.path.join(logs_dir, log_filename)
38
- with open(PrintStyle.log_file_path, "w", encoding="utf-8", errors="replace") as f:
39
- f.write("<html><body style='background-color:black;font-family: Arial, Helvetica, sans-serif;'><pre>\n")
40
-
34
def _get_rgb_color_code(self, color, is_background=False):
35
try:
36
if color.startswith("#") and len(color) == 7:
@@ -90,19 +83,8 @@ class PrintStyle:
83
if self.padding and not self.padding_added:
84
if not self.log_only:
85
print() # Print an empty line for padding
93
- self._log_html("<br>")
86
self.padding_added = True
87
96
- def _log_html(self, html):
97
- with open(PrintStyle.log_file_path, "a", encoding="utf-8", errors="replace") as f: # type: ignore[arg-type]
98
- f.write(sanitize_string(html))
99
-
100
- @staticmethod
101
- def _close_html_log():
102
- if PrintStyle.log_file_path:
103
- with open(PrintStyle.log_file_path, "a", encoding="utf-8", errors="replace") as f:
104
- f.write("</pre></body></html>")
105
-
88
@staticmethod
89
def _format_args(args, sep):
90
if not args:
@@ -155,22 +137,16 @@ class PrintStyle:
137
if not PrintStyle.last_endline:
138
if not self.log_only:
139
print()
158
- self._log_html("<br>")
159
- plain_text, styled_text, html_text = self.get(*args, sep=sep)
140
+ _, styled_text, _ = self.get(*args, sep=sep)
141
if not self.log_only:
142
print(styled_text, end=end, flush=flush)
162
- if end.endswith('\n'):
163
- self._log_html(html_text + "<br>\n")
164
- else:
165
- self._log_html(html_text)
143
PrintStyle.last_endline = end.endswith('\n')
144
145
def stream(self, *args, sep=' ', flush=True):
146
self._add_padding_if_needed()
170
- plain_text, styled_text, html_text = self.get(*args, sep=sep)
147
+ _, styled_text, _ = self.get(*args, sep=sep)
148
if not self.log_only:
149
print(styled_text, end='', flush=flush)
173
- self._log_html(html_text)
150
PrintStyle.last_endline = False
151
152
def is_last_line_empty(self):
@@ -218,7 +194,3 @@ class PrintStyle:
194
def error(*args, sep=' ', end='\n', flush=True):
195
prefixed = PrintStyle._prefixed_args("Error", args)
196
PrintStyle(font_color="red", padding=True).print(*prefixed, sep=sep, end=end, flush=flush)
221
-
222
-# Ensure HTML file is closed properly when the program exits
223
-import atexit
224
-atexit.register(PrintStyle._close_html_log)
helpers/print_style.py.dox.md
+4
-3
@@ -27,12 +27,13 @@
27
28
- Helper modules own reusable framework APIs and must preserve public callers unless all callers, tests, and docs are updated together.
29
- Update this file whenever public functions, classes, persistence behavior, path/security assumptions, side effects, or cross-module contracts change.
30
-- Observed side-effect areas: filesystem reads, WebSocket state, secret handling.
31
-- Imported dependency areas include: `atexit`, `collections.abc`, `datetime`, `html`, `os`, `strings`, `sys`, `webcolors`.
30
+- `PrintStyle` emits sanitized, secret-masked console output and does not create filesystem log files.
31
+- `get()` preserves its plain-text, ANSI-styled, and HTML-styled return values for existing callers.
32
+- Imported dependency areas include: `collections.abc`, `files`, `html`, `strings`, `sys`, `webcolors`.
33
34
## Key Concepts
35
35
-- Important called helpers/classes observed in the source: `atexit.register`, `self._get_rgb_color_code`, `join`, `html.escape.replace`, `sep.join`, `self._format_args`, `sanitize_string`, `self._add_padding_if_needed`, `end.endswith`, `self._log_html`, `sys.stdin.readlines`, `PrintStyle._prefixed_args`, `files.get_abs_path`, `os.makedirs`, `datetime.now.strftime`, `os.path.join`, `f.write`, `self.secrets_mgr.mask_values`, `self._get_styled_text`, `self._get_html_styled_text`.
36
+- Important called helpers/classes observed in the source: `self._get_rgb_color_code`, `html.escape.replace`, `sep.join`, `self._format_args`, `sanitize_string`, `self._add_padding_if_needed`, `end.endswith`, `sys.stdin.readlines`, `PrintStyle._prefixed_args`, `self.secrets_mgr.mask_values`, `self._get_styled_text`, `self._get_html_styled_text`.
37
- Keep request/response, tool, or helper semantics documented here at the same time as source changes.
38
39
## Work Guidance
logs/.gitkeep
skills/a0-debug-plugin/SKILL.md
+3
-3
@@ -158,11 +158,11 @@ print('Done')
158
## 8. Check Agent Zero logs
159
160
```bash
161
-# Find recent log files
162
-ls -lt /a0/logs/*.html | head -5
161
+# Run from the Docker host; replace the name if needed
162
+docker logs --tail 200 a0-instance
163
```
164
165
-Plugin-related errors appear as Python tracebacks mentioning the plugin path.
165
+Plugin-related errors appear in the container output as Python tracebacks mentioning the plugin path.
166
167
---
168
tests/test_migration_cleanup.py
new
+16
@@ -0,0 +1,16 @@
1
+from helpers import files, migration
2
+
3
+
4
+def test_cleanup_obsolete_removes_legacy_logs(tmp_path, monkeypatch):
5
+ logs = tmp_path / "logs"
6
+ logs.mkdir()
7
+ (logs / "old.html").write_text("old log", encoding="utf-8")
8
+ keep = tmp_path / "keep.txt"
9
+ keep.write_text("keep", encoding="utf-8")
10
+ monkeypatch.setattr(files, "_base_dir", str(tmp_path))
11
+
12
+ migration._cleanup_obsolete()
13
+ migration._cleanup_obsolete()
14
+
15
+ assert not logs.exists()
16
+ assert keep.exists()
tests/test_print_style.py
+4
-11
@@ -17,16 +17,12 @@ class _PassthroughSecretsManager:
17
18
@pytest.fixture(autouse=True)
19
def _reset_print_style_state():
20
- PrintStyle.log_file_path = None
20
PrintStyle.last_endline = True
21
yield
23
- PrintStyle.log_file_path = None
22
PrintStyle.last_endline = True
23
24
27
-def test_get_sanitizes_lone_surrogates(tmp_path, monkeypatch):
28
- monkeypatch.setattr("helpers.print_style.files.get_abs_path", lambda _: str(tmp_path))
29
-
25
+def test_get_sanitizes_lone_surrogates():
26
style = PrintStyle(log_only=True)
27
style.secrets_mgr = _PassthroughSecretsManager()
28
@@ -37,16 +33,13 @@ def test_get_sanitizes_lone_surrogates(tmp_path, monkeypatch):
33
assert "\ud83d" not in html_text
34
35
40
-def test_print_writes_html_log_without_surrogate_crash(tmp_path, monkeypatch):
41
- monkeypatch.setattr("helpers.print_style.files.get_abs_path", lambda _: str(tmp_path))
42
-
43
- style = PrintStyle(log_only=True)
36
+def test_print_sanitizes_lone_surrogates_without_crash(capsys):
37
+ style = PrintStyle()
38
style.secrets_mgr = _PassthroughSecretsManager()
39
40
style.print("bad \ud83d")
41
48
- log_path = Path(PrintStyle.log_file_path)
49
- content = log_path.read_text(encoding="utf-8")
42
+ content = capsys.readouterr().out
43
44
assert "bad ?" in content
45
assert "\ud83d" not in content