Skip to content

Studio: keep tools working after opening a chat file in its app - #14390

Open
NilayYadav wants to merge 17 commits into
unslothai:mainfrom
NilayYadav:open-staging-copy-not-link
Open

NilayYadav wants to merge 17 commits into
unslothai:mainfrom
NilayYadav:open-staging-copy-not-link

Conversation

@NilayYadav

Copy link
Copy Markdown
Collaborator

With the OS sandbox on (High on macOS, or Linux with bubblewrap), using "Open in default app" on a file a tool made, such as chart.png, broke the chat. Every later Python or Terminal call failed with "the session workdir contains a file hard-linked from outside it", because the app was handed a hard link to the chat's file. Nothing removed that link until another Open at least a day later.

The app now gets a private copy instead of a link. Links left by earlier versions are removed when Studio starts, so chats already broken work again after updating. A copy that fails partway no longer leaves a partial file behind.

The app sees a snapshot: open the file again to see a newer version, and edits saved from the app stay in Studio's cache (cleared after a day) rather than in the chat folder. Each Open also writes a full copy, so opening a large video several times keeps several copies on disk until the daily cleanup.

@NilayYadav NilayYadav self-assigned this Oct 20, 2026
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-20T23:48:36.732086Z fe75c00 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. What shall we delve into next?

Reviewed commit: ade9781c89

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

chatgpt-codex-connector[bot]

This comment was marked as resolved.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. What shall we delve into next?

Reviewed commit: f39ff8adf9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@unslothai unslothai deleted a comment from chatgpt-codex-connector Bot Oct 20, 2026
@unslothai unslothai deleted a comment from chatgpt-codex-connector Bot Oct 20, 2026
@unslothai unslothai deleted a comment from chatgpt-codex-connector Bot Oct 20, 2026
chatgpt-codex-connector[bot]

This comment was marked as resolved.

@NilayYadav

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 908763f324

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

spare = entry / f".{child.name}.copy"
try:
shutil.copyfile(child, spare, follow_symlinks = False)
os.replace(spare, child)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Isolate descriptors held by legacy apps before tools run

When a default app keeps the legacy staged file descriptor open across a Studio restart, os.replace detaches only the staged pathname; that descriptor still references the same inode as the sandbox file. The replacement drops the sandbox file's link count to one, so scan_workdir_for_host_channels now permits tools even though the host app can still observe tool writes or overwrite the chat file through its descriptor. The migration needs to break the sandbox-side alias as well, or otherwise avoid re-enabling tools while the old inode remains open outside the sandbox.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not changing this. I compared a real run on three versions: the merge base, 5b02fbc, and 7aaeafa. Each run had a chat file notes.csv hard-linked into open-staging/legacy as an older build left it, an fd opened on the staged name and held like an app, then the prune, then a write through that fd. In all three:

  • before the prune the scan refused with "hard-linked from outside it";
  • after the prune the chat file is at nlink=1, the scan returns (), the app fd still points at the chat inode, and its write shows up in the chat file (b'X,b\\n').

The only difference is that on 7aaeafa the staged name still exists, as a private copy, so the app can save by name. The merge base drops the same name after 24 h, and this PR's earlier rmtree dropped it at once, with exactly the same open-fd state. So this change does not create the alias and does not widen it.

Breaking the chat-side alias would mean finding and rewriting the matching file inside some chat's workdir from the cache prune. The scan checks names, because a hard link is how a tool could reach a host path. An fd held by the user's own app, on a file the user opened, gives a tool no way out of the sandbox. Run script: repro/legacy_fd_run.py in the review notes.

@unslothai unslothai deleted a comment from chatgpt-codex-connector Bot Oct 20, 2026
@unslothai unslothai deleted a comment from chatgpt-codex-connector Bot Oct 20, 2026
@unslothai unslothai deleted a comment from chatgpt-codex-connector Bot Oct 20, 2026
chatgpt-codex-connector[bot]

This comment was marked as resolved.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

@unslothai unslothai deleted a comment from chatgpt-codex-connector Bot Oct 20, 2026
@unslothai unslothai deleted a comment from chatgpt-codex-connector Bot Oct 20, 2026
@unslothai unslothai deleted a comment from chatgpt-codex-connector Bot Oct 20, 2026
chatgpt-codex-connector[bot]

This comment was marked as resolved.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

@unslothai unslothai deleted a comment from chatgpt-codex-connector Bot Oct 20, 2026
@unslothai unslothai deleted a comment from chatgpt-codex-connector Bot Oct 20, 2026
@unslothai unslothai deleted a comment from chatgpt-codex-connector Bot Oct 20, 2026
@NilayYadav

Copy link
Copy Markdown
Collaborator Author

@codex security review

chatgpt-codex-connector[bot]

This comment was marked as resolved.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

@unslothai unslothai deleted a comment from chatgpt-codex-connector Bot Oct 20, 2026
@unslothai unslothai deleted a comment from chatgpt-codex-connector Bot Oct 20, 2026
@unslothai unslothai deleted a comment from chatgpt-codex-connector Bot Oct 20, 2026
chatgpt-codex-connector[bot]

This comment was marked as resolved.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

@unslothai unslothai deleted a comment from chatgpt-codex-connector Bot Oct 20, 2026
@unslothai unslothai deleted a comment from chatgpt-codex-connector Bot Oct 20, 2026
@NilayYadav

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🚀

Reviewed commit: fe75c00c34

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@NilayYadav

Copy link
Copy Markdown
Collaborator Author

Review + reproduction evidence

Pinned review: fe75c00c34c3d240cf809554412fb9539851b17c. No open review threads on this commit.

I ran the real flow on the merge base a6c1e06 and on fe75c00c3 (Linux x86_64, Chromium 1440x900, Qwen3.5-2B GGUF, real login): a Python call writes chart.png, then right-click on the chip and Open in default app.

Open in default app on a tool-written file, fe75c00c3

  • Before (run): the staged open-staging/<tmp>/chart.png is the chat's own file (inode 3339, st_nlink 2). With that link in place the sandbox workdir scan raises WorkdirUnsafeError: the session workdir contains a file hard-linked from outside it.
  • PR head: the staged file is its own copy (inode 1613, st_nlink 1) and the chat file stays at st_nlink 1.
    • A second open of the same file reuses that copy (still one staged entry).
    • After the tool rewrote chart.png in place, a third open made a fresh copy (inode 1727).
    • A legacy hard link planted the way an older build left it is gone after a real restart: chat file back to st_nlink 1, staging directory kept.
  • The PR's test file passes on the head (320 passed, 15 new or fixed tests that fail on the merge base). The one remaining failure, test_attachments_are_copied_into_the_sandbox_once[True], fails on the merge base too.

Fixes I pushed during review:

  • f7949a3e4 copy only the bytes the file had when it was opened
  • 33b5eebe1 reuse a staged copy on repeated opens
  • f39ff8adf reuse only when the bytes match
  • 908763f32 fail a copy that is cut short
  • 7aaeafa7f short temp name for long file names
  • e0488eccb never overwrite an app's save
  • b636e7991 legacy cleanup under a lock, never through a symlink
  • 60dd0e644 drop the old outside name instead of copying
  • 68dc6f7e6 clean up before refusing a chat for a staged link
  • 52db6d4df skip a redirected staging root and an unavailable lock
  • 31de614c5 compare against the newest copy only
  • 23b24025e keep the refusal if the rescan runs out of time
  • fe75c00c3 startup cleanup off the startup path

Conclusion: the defect is real on main and the head fixes it at the root. I have not shown the refusal itself live: the Linux host I used has no working bubblewrap, and no macOS run was done, so the before/after inside a real OS sandbox is still unproven.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants