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
33 changes: 33 additions & 0 deletions packages/core/src/__tests__/permissions-integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,39 @@ describe('WorkflowRunner permission lifecycle integration', () => {
expect((runner as any).agentTokens.size).toBe(0);
});

it('warns when a write pattern resolves to no existing paths during dry-run', () => {
const projectDir = createProject({
'src/input.ts': 'export const input = true;\n',
'gate.sh': '#!/usr/bin/env bash\nexit 0\n',
});
const runner = makeRunner(projectDir);
const config = makeConfig([
{
name: 'writer',
cli: 'claude',
permissions: {
access: 'restricted',
files: {
read: ['**'],
write: ['impl.txt'],
deny: ['gate.sh'],
},
},
},
]);

const report = runner.dryRun(config, 'default');
const permissionEntry = report.permissions?.find((entry) => entry.agent === 'writer');

expect(report.valid).toBe(true);
expect(permissionEntry?.writePaths).toBe(0);
expect(report.warnings).toContain(
'Agent "writer" write pattern(s) "impl.txt" resolved to no existing writable paths. '
+ 'The installed @agent-relay/cloud resolver only grants writes to existing paths; '
+ 'files created under these patterns may be denied.'
);
});

it('rejects invalid permission config during validation before provisioning', async () => {
const projectDir = createBaseProject();
const runner = makeRunner(projectDir);
Expand Down
8 changes: 8 additions & 0 deletions packages/core/src/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3080,6 +3080,14 @@ export class WorkflowRunner {

const permissions = resolved.agents.map((agent) => {
const compiled = resolveAgentPermissions(agent.name, agent.permissions, this.cwd, this.workspaceId);
if (compiled.readwritePatterns.length > 0 && compiled.summary.readwrite === 0) {
const patterns = compiled.readwritePatterns.map((pattern) => `"${pattern}"`).join(', ');
warnings.push(
`Agent "${agent.name}" write pattern(s) ${patterns} resolved to no existing writable paths. ` +
'The installed @agent-relay/cloud resolver only grants writes to existing paths; ' +
'files created under these patterns may be denied.'
);
}
const source: NonNullable<DryRunReport['permissions']>[number]['source'] = compiled.sources.some(
(entry) => entry.type === 'yaml'
)
Expand Down