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
10 changes: 10 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ These rules apply to all AI agents working on this repository (Codex, Claude, Co
- **Never commit secrets** (.env, API keys, tokens, credentials).
- **Never skip hooks** (`--no-verify`).

## Bugfix Verification

Every bugfix **must** include a regression test that reproduces the **exact user-reported symptom**, not just a related scenario.

1. **Reproduce first:** Before writing the fix, write a test that fails with the exact symptom the user described. "User does X, expects Y, gets Z" → the test must assert Y and currently produce Z.
2. **Fix, then re-run:** Apply the fix, confirm the test passes.
3. **Green suite is necessary, not sufficient:** All existing tests passing does not prove the reported bug is fixed. The regression test is what proves it.

> **Why this rule exists:** v2.7.3 shipped a doctor fix that used `all_entries_unfiltered()` instead of `all_entries()`. All tests were green, but the actual bug (phantom stale tasks invisible to `palaia list`) was not fixed because no test reproduced the specific scenario (private scope + different agent).

## Pull Requests

- Keep PR titles short (<70 chars), use conventional prefix.
Expand Down
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

### Fixed
- **ContextEngine: `ownsCompaction: true`** — palaia now declares compaction ownership, preventing OpenClaw's built-in Pi auto-compaction from running in parallel with `palaia gc`. Without this flag, both compaction mechanisms could interfere with each other, leading to unpredictable context truncation.
- **Doctor phantom stale-tasks warning** — `_check_stale_unassigned_tasks` now reads entries through `Store.all_entries_unfiltered()` instead of scanning `.md` files directly. The previous approach could report entries invisible to `palaia list` (e.g. entries with empty/invalid scope), causing the doctor to warn about tasks that the user cannot see or act on.
- **Doctor phantom stale-tasks warning** — `_check_stale_unassigned_tasks` now uses `Store.all_entries()` with agent scope filtering (not `all_entries_unfiltered()`). The doctor only reports entries that `palaia list` can actually see — private-scoped entries from other agents are no longer counted as phantom warnings.
- **Scope: empty/unknown scope no longer hides entries** — `can_access()` now treats empty or unrecognized scope values as `"team"` instead of returning `False`. Entries with missing or malformed scope are no longer silently invisible.

---
Expand Down
10 changes: 6 additions & 4 deletions palaia/doctor/checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -1864,9 +1864,9 @@ def _check_claude_code_config(palaia_root: Path | None) -> dict[str, Any]:
def _check_stale_unassigned_tasks(palaia_root: Path | None) -> dict[str, Any]:
"""Check for auto-captured tasks without assignee/due_date older than 7 days.

Uses Store.all_entries_unfiltered() to ensure consistent entry discovery
with `palaia list`. Previous implementation scanned .md files directly,
which could report entries invisible to the user (scope filtering mismatch).
Uses Store.all_entries() with agent scope filtering to ensure the doctor
only reports entries visible to `palaia list`. Entries hidden by scope
(e.g. private entries from a different agent) are excluded.
"""
if palaia_root is None:
return {
Expand All @@ -1878,14 +1878,16 @@ def _check_stale_unassigned_tasks(palaia_root: Path | None) -> dict[str, Any]:

from datetime import datetime, timezone

from palaia.config import resolve_agent as resolve_agent_identity
from palaia.store import Store

now = datetime.now(tz=timezone.utc)
stale_ids: list[str] = []

try:
store = Store(palaia_root)
all_entries = store.all_entries_unfiltered(include_cold=False)
agent = resolve_agent_identity(palaia_root)
all_entries = store.all_entries(include_cold=False, agent=agent)
except Exception:
return {
"name": "stale_unassigned_tasks",
Expand Down
30 changes: 30 additions & 0 deletions tests/test_doctor.py
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,36 @@ def test_ignores_tasks_with_assignee(self, palaia_root):
result = _check_stale_unassigned_tasks(palaia_root)
assert result["status"] == "ok"

def test_ignores_private_tasks_from_other_agent(self, palaia_root, monkeypatch):
"""Regression: doctor must not report private-scoped entries from a
different agent. This was the exact user-reported bug — doctor showed
4 stale tasks that ``palaia list`` didn't because scope filtering was
bypassed (all_entries_unfiltered instead of all_entries).
"""
from datetime import datetime, timedelta, timezone

from palaia.doctor import _check_stale_unassigned_tasks

old_date = (datetime.now(tz=timezone.utc) - timedelta(days=10)).isoformat()
entry_content = f"""---
id: private-other-agent-001
type: task
tags: auto-capture,commitment
created: {old_date}
scope: private
agent: agent-a
title: Task only visible to agent-a
---
This task belongs to agent-a and must be invisible to agent-b."""
(palaia_root / "hot" / "private-other-agent-001.md").write_text(entry_content)

# Doctor runs as agent-b → private entry from agent-a must be invisible
monkeypatch.setenv("PALAIA_AGENT", "agent-b")
result = _check_stale_unassigned_tasks(palaia_root)
assert result["status"] == "ok", (
f"Doctor reported private entry from agent-a while running as agent-b: {result}"
)

def test_ignores_manual_tasks(self, palaia_root):
from datetime import datetime, timedelta, timezone

Expand Down
Loading