Skip to content

Commit 1740899

Browse files
committed
fix: skip codex loaded skill blocks
1 parent ae22f61 commit 1740899

2 files changed

Lines changed: 103 additions & 1 deletion

File tree

plugins/codex/scripts/sync.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
ATTACHMENT_CACHE_FILE = "codex-attachments.json"
3535
IMAGE_PLACEHOLDER_RE = re.compile(r"\[Image:\s*source:\s*([^\]]+)\]")
3636
IMAGE_LABEL_RE = re.compile(r"\[Image\s+#\d+\]")
37+
LOCAL_MARKDOWN_LINK_RE = re.compile(r"\[([^\]]+)\]\((/[^)\n]+)\)")
3738
SUPPORTED_IMAGE_MEDIA_TYPES = {"image/png", "image/jpeg", "image/gif", "image/webp"}
3839
COMPACTION_PLACEHOLDER = (
3940
"[Context compacted - earlier conversation summarized to continue past the context window]"
@@ -424,7 +425,7 @@ def normalize_text_blocks(text: str, image_uploader: ImageUploader | None = None
424425

425426

426427
def append_text_block(blocks: list[dict[str, Any]], text: str) -> None:
427-
value = redact_text(text).strip()
428+
value = redact_text(strip_local_markdown_link_targets(text)).strip()
428429
if value:
429430
blocks.append({"type": "text", "text": value})
430431

@@ -550,12 +551,24 @@ def should_skip_user_message(content: Any) -> bool:
550551
return True
551552
skipped_prefixes = (
552553
"<codex_internal_context",
554+
"<skill>",
553555
"Base directory for this skill:",
554556
"# AGENTS.md instructions for ",
555557
)
556558
return any(text.startswith(prefix) for prefix in skipped_prefixes)
557559

558560

561+
def strip_local_markdown_link_targets(text: str) -> str:
562+
def replace(match: re.Match[str]) -> str:
563+
label = match.group(1).strip()
564+
target = match.group(2).strip()
565+
if target.startswith("/Users/") or target.startswith("/var/") or target.startswith("/private/") or target.startswith("/tmp/"):
566+
return label
567+
return match.group(0)
568+
569+
return LOCAL_MARKDOWN_LINK_RE.sub(replace, text)
570+
571+
559572
def normalize_function_call(payload: dict[str, Any], line_number: int) -> dict[str, Any] | None:
560573
call_id = str(payload.get("call_id") or payload.get("id") or f"call-{line_number}")
561574
name = str(payload.get("name") or "")

plugins/codex/tests/test_plugin_scripts.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,95 @@ def test_build_payload_skips_loaded_agents_instructions_block(self):
265265
self.assertEqual(payload["thread"]["title"], "actual request")
266266
self.assertNotIn("AI AGENT PROTOCOLS", raw_payload)
267267

268+
def test_build_payload_skips_loaded_skill_content_block(self):
269+
from sync import build_payload_from_hook
270+
271+
skill_block = """<skill>
272+
<name>claude-code-setup:spec-driven-planning</name>
273+
<path>/Users/buru/Library/Mobile Documents/com~apple~CloudDocs/dotfiles/config/claude/skills/spec-driven-planning/SKILL.md</path>
274+
---
275+
name: spec-driven-planning
276+
description: Use this skill whenever the user asks for a spec.
277+
---
278+
279+
# Spec-Driven Planning
280+
Do not upload this loaded skill body.
281+
</skill>"""
282+
with tempfile.TemporaryDirectory() as tmpdir:
283+
transcript = Path(tmpdir) / "session.jsonl"
284+
transcript.write_text(
285+
"\n".join(
286+
[
287+
json.dumps({"type": "session_meta", "payload": {"id": "codex-skill", "cwd": "/Users/alice/work/jieli"}}),
288+
json.dumps(
289+
{
290+
"type": "response_item",
291+
"payload": {
292+
"type": "message",
293+
"role": "user",
294+
"content": [{"type": "input_text", "text": skill_block}],
295+
},
296+
}
297+
),
298+
json.dumps(
299+
{
300+
"type": "response_item",
301+
"payload": {
302+
"type": "message",
303+
"role": "user",
304+
"content": [{"type": "input_text", "text": "actual request"}],
305+
},
306+
}
307+
),
308+
]
309+
)
310+
+ "\n",
311+
encoding="utf-8",
312+
)
313+
314+
payload = build_payload_from_hook(
315+
{"session_id": "codex-skill", "transcript_path": str(transcript)},
316+
base_url="https://jieli.example.test",
317+
)
318+
319+
raw_payload = json.dumps(payload, ensure_ascii=False)
320+
self.assertEqual(len(payload["thread"]["messages"]), 1)
321+
self.assertEqual(payload["thread"]["messages"][0]["content"], "actual request")
322+
self.assertNotIn("Spec-Driven Planning", raw_payload)
323+
self.assertNotIn("SKILL.md", raw_payload)
324+
325+
def test_build_payload_strips_local_markdown_link_targets(self):
326+
from sync import build_payload_from_hook
327+
328+
message = "use [$claude-code-setup:spec-driven-planning](/Users/buru/Library/Mobile Documents/com~apple~CloudDocs/dotfiles/config/claude/skills/spec-driven-planning/SKILL.md)"
329+
with tempfile.TemporaryDirectory() as tmpdir:
330+
transcript = Path(tmpdir) / "session.jsonl"
331+
transcript.write_text(
332+
json.dumps({"type": "session_meta", "payload": {"id": "codex-link", "cwd": "/Users/alice/work/jieli"}})
333+
+ "\n"
334+
+ json.dumps(
335+
{
336+
"type": "response_item",
337+
"payload": {
338+
"type": "message",
339+
"role": "user",
340+
"content": [{"type": "input_text", "text": message}],
341+
},
342+
}
343+
)
344+
+ "\n",
345+
encoding="utf-8",
346+
)
347+
348+
payload = build_payload_from_hook(
349+
{"session_id": "codex-link", "transcript_path": str(transcript)},
350+
base_url="https://jieli.example.test",
351+
)
352+
353+
content = payload["thread"]["messages"][0]["content"]
354+
self.assertEqual(content, "use $claude-code-setup:spec-driven-planning")
355+
self.assertNotIn("/Users/buru", json.dumps(payload, ensure_ascii=False))
356+
268357
def test_build_payload_includes_raw_repo_url_from_git_remote(self):
269358
from sync import build_payload_from_hook
270359

0 commit comments

Comments
 (0)