FACTUAL ACTIVITY RECORD · An activity record based on work that took place
Consolidating safe routine operations into constrained workflows
Reducing repeated approvals while preserving workspace, secret, and high-risk operation boundaries
Purpose
Even routine edits and validation inside the workspace, and non-destructive day-to-day GitHub operations, either repeated approval dialogs or stopped when no safe path existed. Broad command permission would also admit dangerous arguments, so routine operations needed narrow interfaces.
The aim was to let every custom role perform normal workspace work while retaining approval boundaries for external-workspace writes, deletion, secrets and credentials, high-risk lifecycle operations, deployment, and publication.
Implementation
.codex/config.toml and all nine custom-role definitions were aligned on workspace-write, keeping normal deliverables and temporary artifacts inside the repository. System temporary directories remained excluded from automatic writes, while external-workspace changes and deletion still required approval.
- source-control workflow helper provides constrained subcommands for repository-local source-control reads, differences, and staging selected paths.
- GitHub workflow helper covers current-repository Tasks, Issue and PR reads, Issue comments, draft creation, checks, read-only APIs, and body-only Issue updates.
- workspace validation helper consolidates file listing, bounded reads, search, syntax, metadata, and policy validation, and safe dry runs behind one entrypoint.
.codex/hooks/pre_tool_use_command_policy.py was connected to PreToolUse as a default-deny policy accepting only reviewed literal entrypoints. Command substitution, pipes, redirects, compound commands, absolute paths, ./ aliases, env, arbitrary interpreters, script files, and unknown executables are rejected before execution.
Workspace reads and searches check path traversal, Git metadata, credential-like names, repository escape, and symlinks case-insensitively. Recursive search for directories or omitted paths uses rg --files -0 only to enumerate names, validates every candidate before content access, and then runs one content search over safe regular files.
- If multiple targets include an invalid path, the operation fails immediately without printing content from safe targets.
- Authentication preflight was limited to read-only checks that neither reveal secrets nor alter credential state.
- Four old single-purpose entrypoints were removed rather than retained as wrappers or fallbacks, with their responsibilities moved into the three canonical workflows.
A final operational check exposed the lack of a safe path for updating an Issue body, so issue-edit-body was added. Its body-only interface accepts only a digits-only target number, the current repository, and a non-empty regular non-symlink body file inside the workspace, while rejecting credential-like files, arbitrary repositories, unknown arguments, and changes to title, state, type, parent, or closure.
ALLOWED_ENTRYPOINTS = frozenset(
{
"generated-output cleanup",
"authentication preflight",
"local Git workflow",
"GitHub workflow",
"post-merge cleanup",
"workspace workflow",
"gh",
}
)
CONTROL_CHARS = frozenset(";&|<>()")
def classify_command(command: Any) -> tuple[bool, str]:
if not isinstance(command, str) or not command.strip():
return False, "Bash command must be a non-empty string."
if "\x00" in command or "\n" in command or "\r" in command:
return False, "Multiline and NUL-containing Bash commands are not allowed."Rendering diagram…
What was confirmed
Verification results
The command-policy self-test passed across 18 denied examples, eight allowed examples, and three hook payloads.
Path-policy tests covered regular files, final and intermediate symlinks, upper- and lower-case credential-like paths and Git metadata, mixed valid and invalid inputs, and recursive safe search, without printing restricted candidate paths or content.
issue-edit-body accepted a valid argument set and rejected nonnumeric identifiers, arbitrary repositories, unknown arguments, external-workspace bodies, and credential-like bodies.
Validation passed for six Bash files, one Python file, two JSON files, ten TOML files, and three repository-local Skills, and the inventory of nine roles with capacity for 12 threads was confirmed.
At the cutoff, the three canonical workflows, default-deny hook, path policy, role configuration, and documentation had been applied to the target. Project trust configuration was confirmed, but the recorded UI could not display hook activation directly, so runtime activation across multiple sessions remained unverified.
Basis for completion
This safety scope was complete when positive paths for routine operations and negative paths for dangerous or ambiguous input passed, the recursive-search and Issue-body-update gaps found during the work were corrected, and the final configuration was applied to the target.