Skip to content

Commit 98036e0

Browse files
committed
fix(core): address round-2 review on declarative agents PR #4842
Two findings: 1. `serializeSubagent` wrote both `permissionMode` and the bridge-derived `approvalMode` into the same frontmatter block. On the next load the parser takes `approvalMode` (explicit wins over bridge), so `permissionMode` silently became dead frontmatter — a user editing it later in the file would be ignored. Skip the `permissionMode` emit when `approvalMode` is also being written; `permissionMode` still round-trips when it's the user's only intent. Two new tests pin both branches. 2. `subagents/index.ts` had a NOTE comment referencing `EFFORT_VALUES` and `parseEffort` as example schema helpers that intentionally aren't re-exported. Those symbols were removed during the v1 scope shrink (637b8b7) and don't exist in the current module. Updated the comment to reference `claudePermissionModeToApprovalMode`, `parseMaxTurns`, and `isPermissionMode`, which are the actual current contents. Sibling-drift note: the same dual-emit pattern exists for `maxTurns` vs `runConfig.max_turns` — round-X revert (70a876d) explicitly deferred the `runConfig.max_turns` prune. Not addressed here per that earlier decision. Refs #4842
1 parent fabd030 commit 98036e0

3 files changed

Lines changed: 34 additions & 6 deletions

File tree

packages/core/src/subagents/index.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,12 @@ export {
3737
// Validation system
3838
export { SubagentValidator } from './validation.js';
3939

40-
// NOTE: declarative-agent schema helpers (EFFORT_VALUES, parseEffort,
41-
// claudePermissionModeToApprovalMode, …) live in `agent-frontmatter-schema.ts`
42-
// and are intentionally NOT re-exported here — they are internal to the
43-
// `SubagentManager` / `claude-converter` parse paths and locking their names
44-
// in the package's public API would constrain follow-up PRs (e.g. when
40+
// NOTE: declarative-agent schema helpers (e.g.
41+
// claudePermissionModeToApprovalMode, parseMaxTurns, isPermissionMode)
42+
// live in `agent-frontmatter-schema.ts` and are intentionally NOT
43+
// re-exported here — they are internal to the `SubagentManager` /
44+
// `claude-converter` parse paths and locking their names in the
45+
// package's public API would constrain follow-up PRs (e.g. when
4546
// `js-yaml` lands and the schema shape changes). Re-introduce specific
4647
// exports here when a cross-package caller actually needs them.
4748

packages/core/src/subagents/subagent-manager.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -839,6 +839,29 @@ You are an agent.
839839
expect(serialized).toContain('maxTurns: 25');
840840
});
841841

842+
it('should NOT emit permissionMode when approvalMode is also being emitted (avoid round-trip drift)', () => {
843+
// Regression for PR #4842 round-2 review: if both fields land on the
844+
// serialised frontmatter, the next parse takes approvalMode (explicit
845+
// wins over bridge) and silently ignores any user edits to
846+
// permissionMode in the file.
847+
const serialized = manager.serializeSubagent({
848+
...validConfig,
849+
permissionMode: 'bypassPermissions',
850+
approvalMode: 'yolo',
851+
});
852+
expect(serialized).toContain('approvalMode: yolo');
853+
expect(serialized).not.toContain('permissionMode:');
854+
});
855+
856+
it('should still emit permissionMode when approvalMode is unset (faithful round-trip of the user intent)', () => {
857+
const serialized = manager.serializeSubagent({
858+
...validConfig,
859+
permissionMode: 'plan',
860+
});
861+
expect(serialized).toContain('permissionMode: plan');
862+
expect(serialized).not.toContain('approvalMode:');
863+
});
864+
842865
it('should not include new fields when undefined', () => {
843866
const serialized = manager.serializeSubagent(validConfig);
844867
expect(serialized).not.toContain('permissionMode:');

packages/core/src/subagents/subagent-manager.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -620,7 +620,11 @@ export class SubagentManager {
620620
}
621621

622622
// CC 2.1.168 declarative-agent fields (round-trip parity).
623-
if (config.permissionMode) {
623+
// Skip permissionMode when approvalMode is already being written: on the
624+
// next load the parser takes approvalMode (explicit wins over bridge),
625+
// making permissionMode dead frontmatter that silently ignores any
626+
// later user edits.
627+
if (config.permissionMode && frontmatter['approvalMode'] === undefined) {
624628
frontmatter['permissionMode'] = config.permissionMode;
625629
}
626630

0 commit comments

Comments
 (0)