Skip to content

Commit 916b938

Browse files
committed
fix(deck): code-quality bot findings + macOS /tmp symlink regression
PR review bot flagged 3 CodeQL alerts; smoke test caught a 4th: * openkb/agent/skill_runner.py: drop unused `Agent` import (we only use Runner and function_tool). * tests/test_deck_cli.py: drop unused `pytest` import. * tests/test_deck_validator.py: drop the inline `import openkb.deck.validator as v` — the module is already imported via `from openkb.deck.validator import ...` at the top. Use `monkeypatch.setattr("openkb.deck.validator. MAX_FILE_BYTES", 100)` instead so there's one canonical reference. * openkb/deck/creator.py: when chaining the critic skill, `result.output_path.relative_to(kb_dir)` blew up on macOS because `run_skill` returns a `.resolve()`-d path (e.g. `/private/tmp/.../`) while `kb_dir` is still the symlink form (`/tmp/.../`). Resolve kb_dir before the relative_to call. Caught by a manual e2e smoke test, NOT by the existing suite — pinned with a new `test_run_deck_create_critique_handles_symlinked_tmp` regression. 539 tests pass.
1 parent 63b62c2 commit 916b938

5 files changed

Lines changed: 45 additions & 7 deletions

File tree

openkb/agent/skill_runner.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
from pathlib import Path
3030
from typing import Any, Optional
3131

32-
from agents import Agent, Runner, function_tool
32+
from agents import Runner, function_tool
3333

3434
from openkb.agent.query import build_query_agent
3535
from openkb.agent.skills import _parse_frontmatter, scan_local_skills

openkb/deck/creator.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,13 @@ async def run_deck_create(
9393
# The producer's output_path tells the critic which file to patch.
9494
# If the producer didn't template a path, fall back to the
9595
# conventional location.
96+
#
97+
# ``result.output_path`` is already ``.resolve()``-d by run_skill;
98+
# ``kb_dir`` may still hold an un-resolved form (e.g. ``/tmp/...``
99+
# on macOS where ``/tmp`` symlinks to ``/private/tmp``). Resolve
100+
# the KB root too so ``relative_to`` doesn't trip on the symlink.
96101
target_path = (
97-
result.output_path.relative_to(kb_dir)
102+
result.output_path.relative_to(kb_dir.resolve())
98103
if result.output_path is not None
99104
else Path(f"output/decks/{deck_name}/index.html")
100105
)

tests/test_deck_cli.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
from pathlib import Path
55
from unittest.mock import AsyncMock, patch
66

7-
import pytest
87
from click.testing import CliRunner
98

109
from openkb.cli import cli

tests/test_deck_creator.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,38 @@ async def missing_skill(**_):
175175
)
176176

177177

178+
@pytest.mark.asyncio
179+
async def test_run_deck_create_critique_handles_symlinked_tmp(tmp_path: Path):
180+
"""Regression: on macOS ``/tmp`` symlinks to ``/private/tmp`` so
181+
``output_path`` comes back resolved while ``kb_dir`` is still the
182+
symlink form. ``relative_to`` must not blow up — both must resolve
183+
before comparing. Caught in smoke testing the e2e flow."""
184+
kb_dir = _make_kb(tmp_path)
185+
# output_path resolved to a deeper-named absolute path (simulates
186+
# the macOS /tmp -> /private/tmp situation)
187+
resolved_target = (kb_dir / "output" / "decks" / "test-deck" / "index.html").resolve()
188+
189+
async def fake_skill(skill_name, intent, **kw):
190+
if skill_name == DEFAULT_DECK_SKILL:
191+
_write_index(kb_dir, "test-deck")
192+
return SkillRunResult(
193+
skill_name=DEFAULT_DECK_SKILL,
194+
output_path=resolved_target,
195+
metadata={"mode": "deck"},
196+
)
197+
return _critic_result()
198+
199+
with patch("openkb.deck.creator.run_skill", new=AsyncMock(side_effect=fake_skill)):
200+
# critique=True is what exercises the relative_to call
201+
await run_deck_create(
202+
kb_dir=kb_dir,
203+
deck_name="test-deck",
204+
intent="t",
205+
model="m",
206+
critique=True,
207+
)
208+
209+
178210
@pytest.mark.asyncio
179211
async def test_run_deck_create_tolerates_missing_critic(tmp_path: Path):
180212
"""Critic skill not installed shouldn't kill the run — the unpatched

tests/test_deck_validator.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -215,9 +215,11 @@ def test_no_slides_no_distinct_warning(tmp_path: Path):
215215

216216

217217
def test_oversize_file_warning(tmp_path: Path, monkeypatch):
218-
# Inject a fake stat() so we don't actually allocate 2MB.
219-
import openkb.deck.validator as v
220-
221-
monkeypatch.setattr(v, "MAX_FILE_BYTES", 100) # threshold 100 bytes for the test
218+
# Lower the size threshold so we don't actually allocate 2MB to
219+
# trigger the warning branch.
220+
monkeypatch.setattr(
221+
"openkb.deck.validator.MAX_FILE_BYTES",
222+
100, # threshold 100 bytes for the test
223+
)
222224
result = validate_deck(_write(tmp_path, GOOD_DECK))
223225
assert any("MB" in w for w in result.warnings)

0 commit comments

Comments
 (0)