Skip to content

Commit b56f214

Browse files
CopilotlpcoxCopilot
authored
Move tool cache mount detection into AWF and add stdin config schema support (#4329)
* Initial plan * feat: add runner tool cache path to config schema * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Landon Cox <landon.cox@microsoft.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent 61ffb44 commit b56f214

12 files changed

Lines changed: 144 additions & 39 deletions

docs/awf-config-spec.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ the corresponding CLI flag.
161161
- `container.tty``--tty`
162162
- `container.dockerHost``--docker-host`
163163
- `container.dockerHostPathPrefix``--docker-host-path-prefix`
164+
- `container.runnerToolCachePath`*(config-only; checked first for optional read-only runner tool cache mount, before `RUNNER_TOOL_CACHE` and `/home/runner/work/_tool` auto-detection)*
164165
- `environment.envFile``--env-file`
165166
- `environment.envAll``--env-all`
166167
- `environment.excludeEnv[]``--exclude-env` *(repeatable)*

docs/awf-config.schema.json

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
"properties": {
5050
"enabled": {
5151
"type": "boolean",
52-
"description": "Enable the API proxy sidecar container. When enabled, source credentials (OPENAI_API_KEY, ANTHROPIC_API_KEY, COPILOT_GITHUB_TOKEN, COPILOT_PROVIDER_API_KEY, GEMINI_API_KEY) are held exclusively in the sidecar and excluded from the agent environment. The agent receives proxy-routing base URLs instead. See docs/awf-config-spec.md \u00a79 for credential isolation semantics."
52+
"description": "Enable the API proxy sidecar container. When enabled, source credentials (OPENAI_API_KEY, ANTHROPIC_API_KEY, COPILOT_GITHUB_TOKEN, COPILOT_PROVIDER_API_KEY, GEMINI_API_KEY) are held exclusively in the sidecar and excluded from the agent environment. The agent receives proxy-routing base URLs instead. See docs/awf-config-spec.md §9 for credential isolation semantics."
5353
},
5454
"enableTokenSteering": {
5555
"type": "boolean",
@@ -70,11 +70,11 @@
7070
"maxEffectiveTokens": {
7171
"type": "integer",
7272
"minimum": 1,
73-
"description": "Maximum cumulative effective tokens allowed for a run. When reached, the API proxy rejects subsequent requests with HTTP 429 and error type 'effective_tokens_limit_exceeded'. Tokens are weighted: input \u00d71, cache-read \u00d70.1, output \u00d74, reasoning \u00d74. See spec \u00a710."
73+
"description": "Maximum cumulative effective tokens allowed for a run. When reached, the API proxy rejects subsequent requests with HTTP 429 and error type 'effective_tokens_limit_exceeded'. Tokens are weighted: input ×1, cache-read ×0.1, output ×4, reasoning ×4. See spec §10."
7474
},
7575
"modelMultipliers": {
7676
"type": "object",
77-
"description": "Per-model multipliers for effective token accounting. Each model's weighted tokens are multiplied by this value before accumulation. Unlisted models use defaultModelMultiplier when set, otherwise the highest configured multiplier. See spec \u00a710.2.",
77+
"description": "Per-model multipliers for effective token accounting. Each model's weighted tokens are multiplied by this value before accumulation. Unlisted models use defaultModelMultiplier when set, otherwise the highest configured multiplier. See spec §10.2.",
7878
"additionalProperties": {
7979
"type": "number",
8080
"exclusiveMinimum": 0
@@ -93,7 +93,7 @@
9393
"maxRuns": {
9494
"type": "integer",
9595
"minimum": 1,
96-
"description": "Maximum number of LLM invocations allowed for a run. When reached, the API proxy rejects subsequent requests with HTTP 429 and error type 'max_runs_exceeded'. See spec \u00a711."
96+
"description": "Maximum number of LLM invocations allowed for a run. When reached, the API proxy rejects subsequent requests with HTTP 429 and error type 'max_runs_exceeded'. See spec §11."
9797
},
9898
"maxPermissionDenied": {
9999
"type": "integer",
@@ -183,7 +183,7 @@
183183
},
184184
"auth": {
185185
"type": "object",
186-
"description": "Authentication configuration for the API proxy sidecar. Enables OIDC-based credential exchange (e.g., GitHub OIDC \u2192 Azure AD, AWS STS, GCP Workload Identity, or Anthropic Workload Identity Federation). See docs/awf-config-spec.md \u00a79.5.",
186+
"description": "Authentication configuration for the API proxy sidecar. Enables OIDC-based credential exchange (e.g., GitHub OIDC Azure AD, AWS STS, GCP Workload Identity, or Anthropic Workload Identity Federation). See docs/awf-config-spec.md §9.5.",
187187
"additionalProperties": false,
188188
"properties": {
189189
"type": {
@@ -480,12 +480,16 @@
480480
"dockerHostPathPrefix": {
481481
"type": "string",
482482
"description": "Prefix bind-mount source paths so the Docker daemon can resolve runner filesystem paths. Required for ARC DinD sidecar runners where the runner and daemon have separate filesystems. Example: \"/host\". Kernel virtual filesystems (/dev, /sys, /proc) are automatically excluded from prefixing. When this points at a daemon-visible shared /tmp path, AWF also stages the invoking CLI binary plus /etc/passwd, /etc/group, and the generated chroot /etc/hosts there."
483+
},
484+
"runnerToolCachePath": {
485+
"type": "string",
486+
"description": "Host runner tool cache directory to mount read-only into chroot mode. When set, AWF checks this path first before environment-based auto-detection."
483487
}
484488
}
485489
},
486490
"environment": {
487491
"type": "object",
488-
"description": "Environment variable propagation into the agent container. Merge behavior is: AWF-reserved variables are set by AWF and are not overridden by envAll or envFile; if envAll is true, host environment variables are forwarded next; envFile is then applied only for variables not already present, so it does not override envAll; CLI -e/--env has highest precedence and may override any variable, including AWF-reserved ones. When apiProxy.enabled is true, source credentials (OPENAI_API_KEY, ANTHROPIC_API_KEY, etc.) are excluded from the agent and held in the API proxy sidecar. See docs/awf-config-spec.md \u00a78\u20139 for credential isolation rules.",
492+
"description": "Environment variable propagation into the agent container. Merge behavior is: AWF-reserved variables are set by AWF and are not overridden by envAll or envFile; if envAll is true, host environment variables are forwarded next; envFile is then applied only for variables not already present, so it does not override envAll; CLI -e/--env has highest precedence and may override any variable, including AWF-reserved ones. When apiProxy.enabled is true, source credentials (OPENAI_API_KEY, ANTHROPIC_API_KEY, etc.) are excluded from the agent and held in the API proxy sidecar. See docs/awf-config-spec.md §8–9 for credential isolation rules.",
489493
"additionalProperties": false,
490494
"properties": {
491495
"envFile": {
@@ -494,7 +498,7 @@
494498
},
495499
"envAll": {
496500
"type": "boolean",
497-
"description": "Forward all host environment variables into the agent container. Use with caution \u2014 may expose secrets."
501+
"description": "Forward all host environment variables into the agent container. Use with caution may expose secrets."
498502
},
499503
"excludeEnv": {
500504
"type": "array",

src/awf-config-schema.json

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
"properties": {
5050
"enabled": {
5151
"type": "boolean",
52-
"description": "Enable the API proxy sidecar container. When enabled, source credentials (OPENAI_API_KEY, ANTHROPIC_API_KEY, COPILOT_GITHUB_TOKEN, COPILOT_PROVIDER_API_KEY, GEMINI_API_KEY) are held exclusively in the sidecar and excluded from the agent environment. The agent receives proxy-routing base URLs instead. See docs/awf-config-spec.md \u00a79 for credential isolation semantics."
52+
"description": "Enable the API proxy sidecar container. When enabled, source credentials (OPENAI_API_KEY, ANTHROPIC_API_KEY, COPILOT_GITHUB_TOKEN, COPILOT_PROVIDER_API_KEY, GEMINI_API_KEY) are held exclusively in the sidecar and excluded from the agent environment. The agent receives proxy-routing base URLs instead. See docs/awf-config-spec.md §9 for credential isolation semantics."
5353
},
5454
"enableTokenSteering": {
5555
"type": "boolean",
@@ -70,11 +70,11 @@
7070
"maxEffectiveTokens": {
7171
"type": "integer",
7272
"minimum": 1,
73-
"description": "Maximum cumulative effective tokens allowed for a run. When reached, the API proxy rejects subsequent requests with HTTP 429 and error type 'effective_tokens_limit_exceeded'. Tokens are weighted: input \u00d71, cache-read \u00d70.1, output \u00d74, reasoning \u00d74. See spec \u00a710."
73+
"description": "Maximum cumulative effective tokens allowed for a run. When reached, the API proxy rejects subsequent requests with HTTP 429 and error type 'effective_tokens_limit_exceeded'. Tokens are weighted: input ×1, cache-read ×0.1, output ×4, reasoning ×4. See spec §10."
7474
},
7575
"modelMultipliers": {
7676
"type": "object",
77-
"description": "Per-model multipliers for effective token accounting. Each model's weighted tokens are multiplied by this value before accumulation. Unlisted models use defaultModelMultiplier when set, otherwise the highest configured multiplier. See spec \u00a710.2.",
77+
"description": "Per-model multipliers for effective token accounting. Each model's weighted tokens are multiplied by this value before accumulation. Unlisted models use defaultModelMultiplier when set, otherwise the highest configured multiplier. See spec §10.2.",
7878
"additionalProperties": {
7979
"type": "number",
8080
"exclusiveMinimum": 0
@@ -93,7 +93,7 @@
9393
"maxRuns": {
9494
"type": "integer",
9595
"minimum": 1,
96-
"description": "Maximum number of LLM invocations allowed for a run. When reached, the API proxy rejects subsequent requests with HTTP 429 and error type 'max_runs_exceeded'. See spec \u00a711."
96+
"description": "Maximum number of LLM invocations allowed for a run. When reached, the API proxy rejects subsequent requests with HTTP 429 and error type 'max_runs_exceeded'. See spec §11."
9797
},
9898
"maxPermissionDenied": {
9999
"type": "integer",
@@ -183,7 +183,7 @@
183183
},
184184
"auth": {
185185
"type": "object",
186-
"description": "Authentication configuration for the API proxy sidecar. Enables OIDC-based credential exchange (e.g., GitHub OIDC \u2192 Azure AD, AWS STS, GCP Workload Identity, or Anthropic Workload Identity Federation). See docs/awf-config-spec.md \u00a79.5.",
186+
"description": "Authentication configuration for the API proxy sidecar. Enables OIDC-based credential exchange (e.g., GitHub OIDC Azure AD, AWS STS, GCP Workload Identity, or Anthropic Workload Identity Federation). See docs/awf-config-spec.md §9.5.",
187187
"additionalProperties": false,
188188
"properties": {
189189
"type": {
@@ -480,12 +480,16 @@
480480
"dockerHostPathPrefix": {
481481
"type": "string",
482482
"description": "Prefix bind-mount source paths so the Docker daemon can resolve runner filesystem paths. Required for ARC DinD sidecar runners where the runner and daemon have separate filesystems. Example: \"/host\". Kernel virtual filesystems (/dev, /sys, /proc) are automatically excluded from prefixing. When this points at a daemon-visible shared /tmp path, AWF also stages the invoking CLI binary plus /etc/passwd, /etc/group, and the generated chroot /etc/hosts there."
483+
},
484+
"runnerToolCachePath": {
485+
"type": "string",
486+
"description": "Host runner tool cache directory to mount read-only into chroot mode. When set, AWF checks this path first before environment-based auto-detection."
483487
}
484488
}
485489
},
486490
"environment": {
487491
"type": "object",
488-
"description": "Environment variable propagation into the agent container. Merge behavior is: AWF-reserved variables are set by AWF and are not overridden by envAll or envFile; if envAll is true, host environment variables are forwarded next; envFile is then applied only for variables not already present, so it does not override envAll; CLI -e/--env has highest precedence and may override any variable, including AWF-reserved ones. When apiProxy.enabled is true, source credentials (OPENAI_API_KEY, ANTHROPIC_API_KEY, etc.) are excluded from the agent and held in the API proxy sidecar. See docs/awf-config-spec.md \u00a78\u20139 for credential isolation rules.",
492+
"description": "Environment variable propagation into the agent container. Merge behavior is: AWF-reserved variables are set by AWF and are not overridden by envAll or envFile; if envAll is true, host environment variables are forwarded next; envFile is then applied only for variables not already present, so it does not override envAll; CLI -e/--env has highest precedence and may override any variable, including AWF-reserved ones. When apiProxy.enabled is true, source credentials (OPENAI_API_KEY, ANTHROPIC_API_KEY, etc.) are excluded from the agent and held in the API proxy sidecar. See docs/awf-config-spec.md §8–9 for credential isolation rules.",
489493
"additionalProperties": false,
490494
"properties": {
491495
"envFile": {
@@ -494,7 +498,7 @@
494498
},
495499
"envAll": {
496500
"type": "boolean",
497-
"description": "Forward all host environment variables into the agent container. Use with caution \u2014 may expose secrets."
501+
"description": "Forward all host environment variables into the agent container. Use with caution may expose secrets."
498502
},
499503
"excludeEnv": {
500504
"type": "array",

src/commands/build-config.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,13 @@ describe('buildConfig', () => {
293293
expect(config.dockerHostPathPrefix).toBe('/host');
294294
});
295295

296+
it('should pass through runnerToolCachePath', () => {
297+
const config = buildConfig(makeInputs({
298+
options: { ...makeInputs().options, runnerToolCachePath: '/opt/hostedtoolcache' },
299+
}));
300+
expect(config.runnerToolCachePath).toBe('/opt/hostedtoolcache');
301+
});
302+
296303
it('should pass through modelAliases', () => {
297304
const aliases = { 'gpt-4': ['gpt-4-turbo'] };
298305
const config = buildConfig(makeInputs({ modelAliases: aliases }));

src/commands/build-config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ export function buildConfig(inputs: BuildConfigInputs): WrapperConfig {
9696
auditDir: (options.auditDir as string | undefined) || process.env.AWF_AUDIT_DIR,
9797
sessionStateDir:
9898
(options.sessionStateDir as string | undefined) || process.env.AWF_SESSION_STATE_DIR,
99+
runnerToolCachePath: options.runnerToolCachePath as string | undefined,
99100
enableHostAccess: options.enableHostAccess as boolean,
100101
localhostDetected,
101102
allowHostPorts: options.allowHostPorts as string | undefined,

src/config-file-mapping.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ describe('mapAwfFileConfigToCliOptions', () => {
256256
tty: true,
257257
dockerHost: 'unix:///var/run/docker.sock',
258258
dockerHostPathPrefix: '/host',
259+
runnerToolCachePath: '/opt/hostedtoolcache',
259260
},
260261
});
261262

@@ -270,6 +271,7 @@ describe('mapAwfFileConfigToCliOptions', () => {
270271
expect(result.tty).toBe(true);
271272
expect(result.dockerHost).toBe('unix:///var/run/docker.sock');
272273
expect(result.dockerHostPathPrefix).toBe('/host');
274+
expect(result.runnerToolCachePath).toBe('/opt/hostedtoolcache');
273275
});
274276

275277
it('maps environment fields', () => {

src/config-file-validation.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,11 @@ describe('validateAwfFileConfig', () => {
386386
expect(errors).toContain('config.container.dockerHostPathPrefix must be a string');
387387
});
388388

389+
it('rejects non-string container.runnerToolCachePath', () => {
390+
const errors = validateAwfFileConfig({ container: { runnerToolCachePath: 123 } });
391+
expect(errors).toContain('config.container.runnerToolCachePath must be a string');
392+
});
393+
389394
it('rejects unknown container keys', () => {
390395
const errors = validateAwfFileConfig({ container: { unknown: true } });
391396
expect(errors).toContain('config.container.unknown is not supported');

src/config-file.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ interface AwfFileConfig {
7979
tty?: boolean;
8080
dockerHost?: string;
8181
dockerHostPathPrefix?: string;
82+
runnerToolCachePath?: string;
8283
};
8384
environment?: {
8485
envFile?: string;
@@ -242,6 +243,7 @@ export function mapAwfFileConfigToCliOptions(config: AwfFileConfig): Record<stri
242243
tty: config.container?.tty,
243244
dockerHost: config.container?.dockerHost,
244245
dockerHostPathPrefix: config.container?.dockerHostPathPrefix,
246+
runnerToolCachePath: config.container?.runnerToolCachePath,
245247

246248
envFile: config.environment?.envFile,
247249
envAll: config.environment?.envAll,

src/schema.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,11 @@ describe('awf-config.schema.json', () => {
190190
expect(validate({ container: { agentTimeout: 1 } })).toBe(true);
191191
});
192192

193+
it('accepts container.runnerToolCachePath as a string', () => {
194+
expect(validate({ container: { runnerToolCachePath: '/opt/hostedtoolcache' } })).toBe(true);
195+
expect(validate({ container: { runnerToolCachePath: 123 } })).toBe(false);
196+
});
197+
193198
it('rejects non-positive-integer rateLimiting values', () => {
194199
expect(validate({ rateLimiting: { requestsPerMinute: 0 } })).toBe(false);
195200
expect(validate({ rateLimiting: { requestsPerMinute: 1 } })).toBe(true);

0 commit comments

Comments
 (0)