Repository navigation
feat(runner): keep each opencode job's saved output its own - #536
Conversation
Quest-Claim: 2f8a892aaa2b4e75
Each opencode job now saves its tool output and shell captures in a data directory beside its checkout, removed with it, while sessions and the credential stay in the account's database through an absolute OPENCODE_DB. The runner's plugin also removes opencode's own tools, which opencode 2.2 offers directly once Code Mode's execute is denied. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars.
|
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed head d10d2d9 against base 1c3cf6a.
No actionable introduced findings in the inspected paths. Per-job data is propagated from Workspace through Launch, retained across attempts, and removed with the workspace. The saved-output permission roots now use that data directory, outside the checkout and within the runner state excluded from shell reads. Login, status and jobs share the explicit account database path, with RunnerHome normalized to an absolute path. The plugin removes tools in opencode's namespace rather than relying on their individual permission names, while retaining ordinary file tools and kix tools.
The added coverage checks configuration, plugin filtering and cleanup, and extends the live boundary test to a second job on the same account/repository reading the first job's saved files through both file and shell tools.
Direction: retain the full live sandbox test as a deployment validation requirement on a host where the real sandbox works. The reported pass-through-srt experiment is useful for integration, but does not demonstrate shell isolation; the PR correctly discloses that limitation.
Verification limits: GitHub-only static inspection of changed code, surrounding sandbox/plugin paths and tests. I did not execute tests or independently validate pinned opencode's runtime tool-transform behavior or operating-system isolation. No host configuration was changed.
(Written by review (OpenAI))
|
quest land watch: the merge queue removed this pull request at d10d2d9 because it conflicts with The land fixer works on it in the background, and the watcher re-enqueues it at the back of the queue once it is fixed or judged flaky, or hands it back here with what it needs. (written by quest land watch) |
|
quest land watch: the merge queue removed this pull request at 0081eea because its The land fixer works on it in the background, and the watcher re-enqueues it at the back of the queue once it is fixed or judged flaky, or hands it back here with what it needs.
…store/lg46w7hrjx7kylsh6645c9l975i8h43b-git-2.55.0/bin/git config --local --get-all includeif.gitdir:/github/workspace/.git.path
/github/runner_temp/git-credentials-7d6dead1-0c75-4435-8713-45157be7a983.config
[command]/nix/store/lg46w7hrjx7kylsh6645c9l975i8h43b-git-2.55.0/bin/git config --local --unset includeif.gitdir:/github/workspace/.git.path \/github\/runner_temp\/git\-credentials\-7d6dead1\-0c75\-4435\-8713\-45157be7a983\.config
[command]/nix/store/lg46w7hrjx7kylsh6645c9l975i8h43b-git-2.55.0/bin/git config --local --get-all includeif.gitdir:/github/workspace/.git/worktrees/*.path
/github/runner_temp/git-credentials-7d6dead1-0c75-4435-8713-45157be7a983.config
[command]/nix/store/lg46w7hrjx7kylsh6645c9l975i8h43b-git-2.55.0/bin/git config --local --unset includeif.gitdir:/github/workspace/.git/worktrees/*.path \/github\/runner_temp\/git\-credentials\-7d6dead1\-0c75\-4435\-8713\-45157be7a983\.config
[command]/nix/store/lg46w7hrjx7kylsh6645c9l975i8h43b-git-2.55.0/bin/git submodule foreach --recursive git config --local --show-origin --name-only --get-regexp remote.origin.url
Removing credentials config '/var/cache/github-runner-work/_temp/git-credentials-7d6dead1-0c75-4435-8713-45157be7a983.config'
##[endgroup]
##[group]Run # Build concurrency and group size are both one in main's ruleset.
�[36;1m# Build concurrency and group size are both one in main's ruleset.�[0m
�[36;1m[[ "$QUEUE_REF" =~ ^refs/heads/gh-readonly-queue/main/pr-([0-9]+)- ]] || { echo "::error::unexpected merge group ref" >&2; exit 1; }�[0m
�[36;1mhead_ref=$(gh api "repos/$REPO/pulls/${BASH_REMATCH[1]}" --jq .head.ref)�[0m
�[36;1m./quest-base/bin/quest --root "$GITHUB_WORKSPACE" check --base "$BASE" --branch "$head_ref"�[0m
shell: /nix/store/1mv3qz005gkbalxfghk4y3s8ayv4d2dl-bash-interactive-5.3p15/bin/bash -e {0}
env:
BASE: 455ebea4fa32580baaa2e2fd85d04612ab0cbc70
GH_TOKEN: ***
REPO: moq-dev/quest
QUEUE_REF: refs/heads/gh-readonly-queue/main/pr-536-455ebea4fa32580baaa2e2fd85d04612ab0cbc70
##[endgroup]
quest: quest/m0/kix/sandbox/README.md: quest title must be '# [XS|S|M|L|XL] Title'
##[error]Process completed with exit code 1.
Post job cleanup.
[command]/nix/store/lg46w7hrjx7kylsh6645c9l975i8h43b-git-2.55.0/bin/git version
git version 2.55.0
Temporarily overriding HOME='/var/cache/github-runner-work/_temp/0782254c-e3a4-458e-89ad-609d4bca3154' before making global git config changes
Adding repository directory to the temporary git global config as a safe directory
[command]/nix/store/lg46w7hrjx7kylsh6645c9l975i8h43b-git-2.55.0/bin/git config --global --add safe.directory /var/cache/github-runner-work/quest/quest
Removing SSH command configuration
[command]/nix/store/lg46w7hrjx7kylsh6645c9l975i8h43b-git-2.55.0/bin/git config --local --name-only --get-regexp core\.sshCommand
[command]/nix/store/lg46w7hrjx7kylsh6645c9l975i8h43b-git-2.55.0/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'core\.sshCommand' && git config --local --unset-all 'core.sshCommand' || :"
Removing HTTP extra header
[command]/nix/store/lg46w7hrjx7kylsh6645c9l975i8h43b-git-2.55.0/bin/git config --local --name-only --get-regexp http\.https\:\/\/github\.com\/\.extraheader
[command]/nix/store/lg46w7hrjx7kylsh6645c9l975i8h43b-git-2.55.0/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'http\.https\:\/\/github\.com\/\.extraheader' && git config --local --unset-all 'http.https://github.com/.extraheader' || :"
Removing includeIf entries pointing to credentials config files
[command]/nix/store/lg46w7hrjx7kylsh6645c9l975i8h43b-git-2.55.0/bin/git config --local --name-only --get-regexp ^includeIf\.gitdir:
[command]/nix/store/lg46w7hrjx7kylsh6645c9l975i8h43b-git-2.55.0/bin/git submodule foreach --recursive git config --local --show-origin --name-only --get-regexp remote.origin.url
Post job cleanup.
[command]/nix/store/lg46w7hrjx7kylsh6645c9l975i8h43b-git-2.55.0/bin/git version
git version 2.55.0
Temporarily overriding HOME='/var/cache/github-runner-work/_temp/106a3c64-7054-4844-8eec-0a61235cb314' before making global git config changes
Adding repository directory to the temporary git global config as a safe directory
[command]/nix/store/lg46w7hrjx7kylsh6645c9l975i8h43b-git-2.55.0/bin/git config --global --add safe.directory /var/cache/github-runner-work/quest/quest
Removing SSH command configuration
[command]/nix/store/lg46w7hrjx7kylsh6645c9l975i8h43b-git-2.55.0/bin/git config --local --name-only --get-regexp core\.sshCommand
[command]/nix/store/lg46w7hrjx7kylsh6645c9l975i8h43b-git-2.55.0/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'core\.sshCommand' && git config --local --unset-all 'core.sshCommand' || :"
Removing HTTP extra header
[command]/nix/store/lg46w7hrjx7kylsh6645c9l975i8h43b-git-2.55.0/bin/git config --local --name-only --get-regexp http\.https\:\/\/github\.com\/\.extraheader
[command]/nix/store/lg46w7hrjx7kylsh6645c9l975i8h43b-git-2.55.0/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'http\.https\:\/\/github\.com\/\.extraheader' && git config --local --unset-all 'http.https://github.com/.extraheader' || :"
Removing includeIf entries pointing to credentials config files
[command]/nix/store/lg46w7hrjx7kylsh6645c9l975i8h43b-git-2.55.0/bin/git config --local --name-only --get-regexp ^includeIf\.gitdir:
[command]/nix/store/lg46w7hrjx7kylsh6645c9l975i8h43b-git-2.55.0/bin/git submodule foreach --recursive git config --local --show-origin --name-only --get-regexp remote.origin.url
A job completed hook has been configured by the self-hosted runner administrator
##[group]Run '/nix/store/gx2v6q84b0w43bafm0whkdx93qyxbpps-ci-cache-gc.sh/bin/ci-cache-gc.sh'
shell: /nix/store/1mv3qz005gkbalxfghk4y3s8ayv4d2dl-bash-interactive-5.3p15/bin/bash --noprofile --norc -e -o pipefail {0}
##[endgroup]
evicted 0 objects and 0 action results (0 B logical); 50.0 GiB logical remain
Cleaning up orphan processes
Current runner version: '2.338.2'
##[group]Runner Image Provisioner
Hosted Compute Agent
Version: 20261028.624
Commit: 54898b74c23fde5556c9b3c3a2290865feef0dfe
Build Date: 2026-10-28T21:51:42Z
Worker ID: {cf4626c1-37a1-49fa-8cf5-4fd65b513c7e}
Region: westus2
Cloud: Azure
##[endgroup]
##[group]Operating System
Ubuntu
24.04.5
LTS
##[endgroup]
##[group]Runner Image
Image: ubuntu-24.04
Version: 20261101.360.1
Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20261101.360/images/ubuntu/Ubuntu2404-Readme.md
Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20261101.360
##[endgroup]
##[group]GITHUB_TOKEN Permissions
Contents: read
Metadata: read
PullRequests: read
##[endgroup]
Secret source: Actions
Cache mode: write
Prepare workflow directory
Prepare all required actions
Complete job name: Queue
##[group]Run [[ "$RESULT" == success ]] || { echo "::error::the queue build ended $RESULT" >&2; exit 1; }
�[36;1m[[ "$RESULT" == success ]] || { echo "::error::the queue build ended $RESULT" >&2; exit 1; }�[0m
shell: /usr/bin/bash -e {0}
env:
RESULT: failure
##[endgroup]
##[error]the queue build ended failure
##[error]Process completed with exit code 1.
Cleaning up orphan processes
(written by quest land watch) |
|
codex, gpt-6.1-sol, medium effort land fixer note:
(written by codex, gpt-6.1-sol, medium effort) |
|
codex, gpt-6.1-sol, medium effort land fixer: quest: quest/m0/kix/sandbox/README.md: quest title must be '# [XS|S|M|L|XL] Title' ##[error]Process completed with exit code 1. (written by codex, gpt-6.1-sol, medium effort) |
# Conflicts: # quest/m0/kix/sandbox/README.md
Quest-Claim: e6a6c03dae5b528a
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merges #560's branch so the epic's deletion holds whichever lands first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Incremental review of 3d5a993 against base 2108b1d, comparing previously reviewed d10d2d9 with base changes accounted for.
No new actionable findings. The per-job isolation implementation is unchanged. The new Claude live-test launch supplies the required data directory; the lockfile's empty trustedDependencies agrees with package.json. The incorporated PR-trigger workflow is the same optional, read-only workflow reviewed in #560. Removing the completed m0 epic preserves its design decisions in m1, updates dependencies, and correctly distinguishes the existing OS sandbox from future untrusted-devShell VM isolation.
Direction: retain real sandbox boundary testing as a rollout requirement. Including the optional PR-trigger workflow does not itself prove OS isolation or guarantee that landing waits for it.
Verification limits: GitHub-only static, base-aware patch comparison and integration inspection. No build, tests, live Actions run or sandbox execution. The prior pass-through-srt experiment remains an integration check, not evidence of shell confinement. COMMENT only, not approval.
(Written by review (OpenAI))
|
quest land watch: the merge queue removed this pull request at 3d5a993 because it conflicts with The land fixer works on it in the background, and the watcher re-enqueues it at the back of the queue once it is fixed or judged flaky, or hands it back here with what it needs. (written by quest land watch) |
|
quest land watch: armed auto-merge on this pull request at 4fae41b, so it joins the back of the merge queue once its required checks pass, with the land fixer's fix ( (written by quest land watch) |

Each opencode job now saves its tool output and shell captures in its own directory, so no job can read output another job saved, whether on another repository or the same one. Sessions and the account's credential stay in the account's shared database.
Changes
Workspace.data(<checkout>.data) sits beside the checkout, in the runner's state directory, which commands cannot read.Workspace.removedeletes it along with the checkout. The job passes it to the adapter asLaunch.data.XDG_DATA_HOMEto that directory.savedOutput, theexternal_directoryallow rules, and the plugin'sreadsnow name only this job'sopencode/tool-outputandopencode/shell.OPENCODE_DBpoints at the account'sopencode.dbby absolute path for sign-in, status, and jobs alike (accountDatabase). A relative name would resolve inside the job's directory. Without the variable, opencode picks a database name per release channel.RunnerHomeresolves its directory to an absolute path, so a relativeQUEST_RUNNER_HOMEcannot turnOPENCODE_DBor the sandbox's paths relative.executeis denied, 2.2.x offers Code Mode's tools directly (packages/core/src/tool.ts, "expose them as direct tools instead"). Jobs were therefore offeredopencode_session_move,opencode_session_rename,opencode_worktree_create/list/remove,opencode_models, and the MCP resource tools, all running in the unconfined server. They could move or rename any session in the shared database and create or remove worktrees anywhere. The quest's decision to share the database relied on those tools being unreachable. The runner's plugin now removes every tool in opencode's namespace throughctx.tool.transform. A deny rule could not catch all of them, becauseworktree_removechecks its permission asedit. On main, the live test already fails its offered-tools check on 2.2.1.test/live/runner-sandbox.test.tsnow runs a second job in another checkout of the same repository, on the same account. It tries to read the first job's real shell capture, plus a tool-output file the test puts where opencode would, through both thereadtool andcat. opencode savestool-outputonly for tools that bound none of their own output, such as kix's MCP tools, and the scripted job cannot call those. That is why the test plants the tool-output file instead of having job 1 produce one.Public API and wire impact
None outside the runner.
Launchgains a requireddatafield. On-disk state moves (no migration; nothing is in production).Decisions
quest checkrefuses once it is childless).fb27360c) into this one so the deletion holds whichever lands first (rejected: delete the epic without it, which gives a modify/delete conflict on the README in either order; leave the epic, which breaks the queue's base check once both land). If ci(sandbox): run the boundary tests on PRs that move the agents #560 lands first, the merge adds nothing; if this lands first, ci(sandbox): run the boundary tests on PRs that move the agents #560's reviewed commits land with it.test/live/runner-sandbox-claude.test.ts(from test(runner): check the Claude Code sandbox's boundary nightly #543) to pass the job's newdatadirectory tolaunch.Choices made without asking
<checkout>.data, by its role, following<checkout>.review.OPENCODE_DBis set for sign-in and status too, not only for jobs, so all three use one database whatever channel opencode was built for.Validation
just checkandjust testpass (1100 passed, 6 skipped).just runner-sandboxcannot run on this host. The host's AppArmorbwrap-userns-restrictprofile makes srt's bwrap fail withbwrap: loopback: Failed RTM_NEWADDR: Operation not permitted. It fails inunixSocketRefusal, before any code this PR changes, and fails the same way on main. CI runs it nightly.srt(a stand-in that confines nothing), in a throwaway copy of the test:checkouts/job.data/opencode/shell/<project>/. The account directory holds onlyopencode.db,log, andrepos.readof both files of job 1.catreads got through, but only because the stand-in confines nothing. The real srt denies the runner's state directory, which holds the data directory.opencode_*tools listed.Caused by
data/opencode, and the plugin let jobs read all saved output in it.executeis denied, without a run of the live sandbox test.Suggested follow-ups
just runner-sandboxon PRs that changeflake.lockor@opencode/client, not only nightly. Recommendation: plan it. moq-dev/quest#502 merged an unreviewed opencode release, and that exposed unconfined tools.Models
Review
mechanical fix by codex, gpt-6.1-sol, medium effort:
Merged base and re·solved the modify/delete conflict by retaining the m0 sandbox quest deletion; its m1 replacement already preserves base's updated wording; ran just check and just test (passed).(head 4fae41b)Rewritten by
quest reviewafter each round.01a1b820-bae3-7643-a7a4-ca9e3f92e4d5, round 1No actionable regressions were found; the output isolation, shared account database, cleanup, and PR workflow changes appear consistent, though builds and tests were not run as instructed.