Skip to content

Commit b1d869b

Browse files
author
Tehan
committed
fix(skill-memory): reject ctx_skill_note when skill-memory is disabled in frontmatter
Counterpart to ctx_skill_recall's enabled-guard (greptile review, PR cortexkit#181): without it, notes for skills that never opted in inserted successfully but were permanently orphaned — recallSkillMemoryBlock returns "" when frontmatter is disabled, while the agent saw a convincing 'Skill note saved' response. Now returns an actionable error before any insert. Red-checked: new regression test fails without the guard (orphan row inserted + 'saved' response), passes with it (no row, 'not enabled').
1 parent bbcbc14 commit b1d869b

2 files changed

Lines changed: 48 additions & 0 deletions

File tree

packages/plugin/src/tools/ctx-skill-note/tools.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,43 @@ describe("ctx_skill_note tool", () => {
6969
}
7070
});
7171

72+
test("rejects note when skill-memory is disabled in frontmatter (no orphan insert)", async () => {
73+
const db = makeDb();
74+
const registry: SkillLoadRegistry = createSkillLoadRegistry();
75+
try {
76+
// Registry entry WITHOUT skill-memory opt-in (frontmatterConfig null —
77+
// the shape the after-hook stores for skills that never enabled it).
78+
registry.set(registryKey("ses_test", "tdd"), {
79+
resolvedPath: "/home/user/.config/opencode/skills/tdd/SKILL.md",
80+
tier: "global",
81+
skillSource: "opencode-global",
82+
skillId: "tdd",
83+
loadedAt: Date.now(),
84+
frontmatterConfig: null,
85+
});
86+
87+
const t = createCtxSkillNoteTool({ db, skillLoadRegistry: registry });
88+
const result = await t.execute(
89+
{
90+
skill: "tdd",
91+
intent: "fix flaky test",
92+
kind: "gotcha",
93+
delta: "Always mock the clock",
94+
},
95+
toolContext(),
96+
);
97+
expect(result).toContain("not enabled");
98+
expect(result).not.toContain("saved");
99+
// No orphaned row inserted
100+
const row = db
101+
.prepare("SELECT COUNT(*) AS n FROM skill_memory WHERE skill_id = 'tdd'")
102+
.get() as { n: number };
103+
expect(row.n).toBe(0);
104+
} finally {
105+
closeQuietly(db);
106+
}
107+
});
108+
72109
test("inserts note when skill is in registry", async () => {
73110
const db = makeDb();
74111
const registry: SkillLoadRegistry = createSkillLoadRegistry();

packages/plugin/src/tools/ctx-skill-note/tools.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,17 @@ export function createCtxSkillNoteTool(deps: CtxSkillNoteToolDeps): ToolDefiniti
8585
);
8686
}
8787

88+
// Counterpart to ctx_skill_recall's enabled-guard: without it, notes for
89+
// skills that never opted in would insert successfully but be permanently
90+
// orphaned (recallSkillMemoryBlock returns "" when frontmatter is disabled),
91+
// while the agent sees a convincing "Skill note saved" response.
92+
if (!registryEntry.frontmatterConfig?.enabled) {
93+
return (
94+
`skill-memory is not enabled for '${args.skill}', so this note would never be surfaced. ` +
95+
`To enable it, add \`skill-memory: { enabled: true }\` to the skill's SKILL.md frontmatter, reload the skill, then record the note.`
96+
);
97+
}
98+
8899
// Use toolContext.directory (the session's working directory) rather than
89100
// a launch dir. This matches ctx_memory's pattern and correctly handles
90101
// `opencode -s` launched outside the project root.

0 commit comments

Comments
 (0)