Skip to content

feat(podman): honor OCI image working directories - #3982

Draft
matthewgrossman wants to merge 2 commits into
podman-workdir/b-volume-ownershipfrom
podman-workdir/c-oci-workdir
Draft

matthewgrossman wants to merge 2 commits into
podman-workdir/b-volume-ownershipfrom
podman-workdir/c-oci-workdir

Conversation

@matthewgrossman

@matthewgrossman matthewgrossman commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Podman sandboxes now use an image's custom WORKDIR as the agent workspace, as Docker already does. Images without a custom working directory still use the managed /sandbox volume. Docker behavior does not change.

When the image user can't use the image's WORKDIR, sandbox creation now fails with WorkspaceValidationFailed on both Docker and Podman, instead of a generic startup failure.

This is the third of four stacked PRs split out of #3801: #3979 (remove unreachable sandbox code) ← #3981 (Podman workspace volume ownership) ← this PR ← #3931 (shared OCI image tests). Together, the four supersede #3801.

Why this matters

Custom WORKDIR on Podman. An image can set WORKDIR /home/app/project and prepare that directory with files and permissions for its user. Today, a Podman sandbox ignores that choice and puts the agent in /sandbox instead. Files the image placed in its working directory are not in the agent's workspace, so commands and uploads can go to the wrong place. Docker already works from the image's chosen directory.

With this change, Podman keeps a custom workspace in the image's own filesystem rather than mounting a new volume over it. That preserves the image's files and ownership; OpenShell does not create the directory or grant the user new access. If the image leaves WORKDIR unset, sets it to /, or chooses /sandbox, the existing managed-volume behavior remains, with the ownership fix from #3981.

A clear error for an unusable WORKDIR. When someone points OpenShell at an image whose WORKDIR the image user cannot write, sandbox creation should say so. The RFC 0012 runtime split dropped the supervisor exit status that drivers used to report this:

  • Docker reported ControlSupervisorStartFailed, with the real cause buried in a log tail.
  • Podman reported a signal kill, which it treats as a recoverable runtime restart, and lost the cause entirely.

Users got no hint that the image was the problem.

What you can now do

On a Podman gateway host, build an image with a writable custom working directory:

FROM ubuntu:24.04
RUN mkdir -p /home/app/project && chown -R 1000:1000 /home/app
WORKDIR /home/app/project
USER 1000:1000
podman build -t localhost/agent:workdir .
openshell sandbox create --name workdir-demo --from localhost/agent:workdir --detach -- sleep infinity
openshell sandbox exec --name workdir-demo -- sh -c 'pwd; touch session.txt'
openshell sandbox exec --name workdir-demo -- sh -c 'test -f session.txt && echo persisted'

Previously, Podman used /sandbox regardless of the image's WORKDIR.

Related Issue

Replaces #3933, which GitHub automatically marked merged during stack reordering.

Fixes #2526. Supersedes #3801 and the Podman approach in #2715, which needed extra directory checks because the workload and supervisor shared a container. RFC 0012 runs them in separate containers, so those checks are no longer needed.

Follow-ups kept out of this PR:

Changes

WorkspaceValidationFailed reporting

  • openshell-core: shared constants for the error context and the fixed condition message, next to the existing reserved exit status 78.
  • openshell-sandbox: tags the rejection with the shared error context.
  • openshell-supervisor: exits with status 78 when an agent start fails with that context.
  • openshell-driver-docker: maps a supervisor exit of 78 to WorkspaceValidationFailed, both while waiting for readiness and while monitoring.
  • openshell-driver-podman: reads the supervisor companion's exit status, not the workload's (the watcher stops the workload with SIGKILL), and maps 78 to WorkspaceValidationFailed.
  • Podman uses the fixed condition message; Docker also includes the supervisor log tail.

Podman OCI WORKDIR

  • Resolve the Podman image ID, user, environment, and working directory from one pinned image inspection (ResolvedPodmanImage).
  • Keep a custom workspace in the image's container filesystem: no workspace volume, no archive upload, and no root setup step. The workload starts as the final non-root user, and the existing sandbox-runtime check still requires that user to be able to traverse and write the directory.
  • Validate a custom workdir with the shared OCI rules, and reject Podman control-path overlaps (/.openshell, supervisor CA runtime root). Reject image VOLUMEs or driver mounts that cover the workspace; mounts nested below it remain valid.
  • Pass the resolved path to agent commands via --workdir. Start the runtime from / so Podman neither creates a missing WORKDIR nor fails to chdir into it before OpenShell validates it.
  • Add podman_oci_identity to the Podman CI test list, and give its image a custom WORKDIR with no workspace mounts.
  • Update the Podman README, runtime docs, and debug skill.

#3931 adds the shared oci-image suite on top of this PR and runs it on Docker and both Podman lanes in CI.

Testing

All checks ran against a local rootful Podman machine (macOS, libkrun). Rootless coverage comes from the fedora-podman-rootless oci-image lane added in #3931.

  • mise run pre-commit passes
  • Unit tests: cargo test passes for openshell-core, openshell-driver-docker, and openshell-driver-podman. That includes the supervisor exit mapping, Docker readiness and monitor reasons, the Podman supervisor-exit condition, the workspace spec, custom vs. managed launch, and image volumes and driver mounts that cover the workdir. Clippy is clean for those crates, and for openshell-sandbox and openshell-supervisor on aarch64-unknown-linux-gnu.
  • E2E: podman_oci_identity passes.
  • Feature tests: with a gateway built from this branch, the oci-image suite from test(oci): share OCI USER and WORKDIR checks across Docker and Podman #3931 passes 4/4, including the WorkspaceValidationFailed reason for an unwritable WORKDIR.
  • Docker: there's no local Docker daemon on the machine I used, so the ubuntu-docker-rootful / oci-image lane in test(oci): share OCI USER and WORKDIR checks across Docker and Podman #3931 is the end-to-end check for the Docker side of WorkspaceValidationFailed.

Checklist

  • Follows Conventional Commits
  • Commits are signed off (DCO)
  • Architecture docs updated (if applicable)

@copy-pr-bot

copy-pr-bot Bot commented Sep 30, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@matthewgrossman
matthewgrossman added this pull request to stack #3983 September 30, 2026 18:53
@github-actions

Copy link
Copy Markdown

…ValidationFailed

The RFC 0012 split dropped the supervisor exit status that drivers map to
the WorkspaceValidationFailed condition. An image WORKDIR the sandbox
identity cannot use then surfaced as ControlSupervisorStartFailed on
Docker and as a signal kill on Podman.

The supervisor now exits with the reserved status when the sandbox rejects
the image working directory. Docker maps that supervisor exit during
readiness and monitoring, and Podman maps the supervisor companion's exit
instead of the workload's.

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
Podman sandboxes now use a custom OCI image WORKDIR as the workspace and
as the working directory for agent commands, matching Docker. Empty, /,
and /sandbox values keep the managed /sandbox workspace volume.

A custom workspace stays in the image's container filesystem: no
workspace volume, no archive upload, and no root setup step. Resolve the
image ID, user, environment, and working directory from one pinned
inspection, validate the workdir with the shared OCI rules, and reject
Podman control-path overlaps and image volumes or driver mounts that
cover it. The runtime starts from / and passes the resolved path to agent
commands with --workdir.

Add podman_oci_identity to the Podman CI test list.

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
@matthewgrossman
matthewgrossman force-pushed the podman-workdir/c-oci-workdir branch from d4ffa52 to f192442 Compare October 1, 2026 19:15
@matthewgrossman

Copy link
Copy Markdown
Contributor Author

/ok to test f192442

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: honor OCI WorkingDir for Docker and Podman workspaces

1 participant