feat: default to workspace-write when the working directory is a Git repo

This commit is contained in:
Michael Bolin
2025-10-03 13:38:15 -07:00
parent 6fb86fda8b
commit f2f894aa69
5 changed files with 90 additions and 10 deletions
+1
View File
@@ -38,6 +38,7 @@ Provide either `prompt` or `prompt_file`; the other may be left empty. The actio
| `prompt_file` | Conditionally | Path (relative to the repository root) of a file that contains the prompt. Provide this or `prompt`. | `""` |
| `output_file` | No | File where the final Codex message is written. Leave empty to skip writing a file. | `""` |
| `working_directory` | No | Directory passed to `codex exec --cd`. Defaults to the repository root. | `""` |
| `sandbox` | No | Sandbox mode for Codex. One of `workspace-write` (default), `read-only` or `danger-full-access`. | `""` |
| `codex_version` | No | Version of `@openai/codex` to install. | `0.42.0-alpha.3` |
| `codex_args` | No | Extra arguments forwarded to `codex exec`. Accepts JSON arrays (`["--flag", "value"]`) or shell-style strings. | `""` |
| `output_schema` | No | Inline schema contents written to a temp file and passed to `codex exec --output-schema`. Mutually exclusive with `output_schema_file`. | `""` |
+7
View File
@@ -21,6 +21,11 @@ inputs:
description: "Optional working directory to `cd` into before running `codex exec`."
required: false
default: ""
sandbox:
description: |
Sandbox mode for Codex. One of `workspace-write` (default), `read-only` or `danger-full-access`.
required: false
default: "workspace-write"
codex_version:
description: "Version of `@openai/codex` to install."
required: false
@@ -185,6 +190,7 @@ runs:
CODEX_OUTPUT_FILE: ${{ inputs.output_file }}
CODEX_HOME: ${{ inputs.codex_home }}
CODEX_WORKING_DIRECTORY: ${{ inputs.working_directory || github.workspace }}
CODEX_SANDBOX: ${{ inputs.sandbox }}
CODEX_ARGS: ${{ inputs.codex_args }}
CODEX_OUTPUT_SCHEMA: ${{ inputs.output_schema }}
CODEX_OUTPUT_SCHEMA_FILE: ${{ inputs.output_schema_file }}
@@ -204,6 +210,7 @@ runs:
--extra-args "$CODEX_ARGS" \
--output-schema "$CODEX_OUTPUT_SCHEMA" \
--output-schema-file "$CODEX_OUTPUT_SCHEMA_FILE" \
--sandbox "$CODEX_SANDBOX" \
--model "$CODEX_MODEL" \
--safety-strategy "$CODEX_SAFETY_STRATEGY" \
--codex-user "$CODEX_USER"
+35 -5
View File
File diff suppressed because one or more lines are too long
+21
View File
@@ -3,6 +3,7 @@ import pkg from "../package.json" assert { type: "json" };
import { readServerInfo } from "./readServerInfo";
import {
SandboxMode,
OutputSchemaSource,
PromptSource,
runCodexExec,
@@ -83,6 +84,10 @@ export async function main() {
"--output-schema <SCHEMA>",
"Inline schema contents to pass to `codex exec --output-schema`."
)
.requiredOption(
"--sandbox <SANDBOX>",
"Sandbox mode override to pass to `codex exec`."
)
.requiredOption("--model <model>", "Model the agent should use")
.requiredOption(
"--safety-strategy <strategy>",
@@ -103,6 +108,7 @@ export async function main() {
outputFile: string;
outputSchemaFile: string;
outputSchema: string;
sandbox: string;
model: string;
safetyStrategy: string;
codexUser: string;
@@ -117,6 +123,7 @@ export async function main() {
extraArgs,
outputSchema,
outputSchemaFile,
sandbox,
model,
safetyStrategy,
codexUser,
@@ -170,6 +177,7 @@ export async function main() {
extraArgs,
explicitOutputFile: emptyAsNull(outputFile),
outputSchema: outputSchemaSource,
sandbox: toSandboxMode(sandbox),
model: emptyAsNull(model),
safetyStrategy: toSafetyStrategy(safetyStrategy),
codexUser: emptyAsNull(codexUser),
@@ -242,6 +250,19 @@ function toSafetyStrategy(value: string): SafetyStrategy {
}
}
function toSandboxMode(value: string): SandboxMode {
switch (value) {
case "read-only":
case "workspace-write":
case "danger-full-access":
return value;
default:
throw new Error(
`Invalid sandbox: ${value}. Must be one of 'read-only', 'workspace-write', or 'danger-full-access'.`
);
}
}
function emptyAsNull(value: string): string | null {
return value.trim().length == 0 ? null : value;
}
+26 -5
View File
@@ -19,6 +19,11 @@ export type SafetyStrategy =
| "unprivileged_user"
| "unsafe";
export type SandboxMode =
| "read-only"
| "workspace-write"
| "danger-full-access";
export type OutputSchemaSource =
| {
type: "file";
@@ -40,6 +45,7 @@ export async function runCodexExec({
model,
safetyStrategy,
codexUser,
sandbox,
}: {
prompt: PromptSource;
codexHome: string | null;
@@ -51,6 +57,7 @@ export async function runCodexExec({
model: string | null;
safetyStrategy: SafetyStrategy;
codexUser: string | null;
sandbox: SandboxMode;
}): Promise<void> {
let input: string;
switch (prompt.type) {
@@ -70,6 +77,10 @@ export async function runCodexExec({
}
const resolvedOutputSchema = await resolveOutputSchema(outputSchema);
const sandboxMode = await determineSandboxMode({
safetyStrategy,
requestedSandbox: sandbox,
});
const command: Array<string> = [];
@@ -111,11 +122,7 @@ export async function runCodexExec({
command.push(...extraArgs);
// Note that if profiles expand to support their own sandbox policies, a
// custom profile could override this setting.
if (safetyStrategy === "read_only") {
command.push("--config", 'sandbox_mode="read-only"');
}
command.push("--sandbox", sandboxMode);
const env = { ...process.env };
let extraEnv = "";
@@ -244,3 +251,17 @@ async function cleanupOutputSchema(
return;
}
}
async function determineSandboxMode({
safetyStrategy,
requestedSandbox,
}: {
safetyStrategy: SafetyStrategy;
requestedSandbox: SandboxMode;
}): Promise<SandboxMode> {
if (safetyStrategy === "read_only") {
return "read-only";
} else {
return requestedSandbox;
}
}