fix(server): expose persisted image attachment paths to provider models - #2094

Closed
roni-estein wants to merge 3 commits into
pingdotgg:mainfrom
roni-estein:fix/persist-image-attachment-paths
Closed

fix(server): expose persisted image attachment paths to provider models#2094
roni-estein wants to merge 3 commits into
pingdotgg:mainfrom
roni-estein:fix/persist-image-attachment-paths

Conversation

@roni-estein

@roni-esteinroni-estein commented Apr 16, 2026

Copy link
Copy Markdown

What Changed

When users attach images in chat, the server already persists them to disk in the attachments directory, but only sends base64-encoded data to the provider model. The model can see the image visually but has no filesystem path to reference, so it cannot copy or move the file into the project workspace when asked.

This PR adds a text annotation alongside each image that includes the on-disk path where the attachment was saved:

[Attached image "screenshot.png" is saved at: /path/to/attachments/thread-abc-uuid.png]

ClaudeAdapter — appends a { type: "text" } content block after each image block in the SDK message.

CodexAdapter — appends path annotations to the input text (since Codex uses a separate attachments array rather than inline content blocks).

Why

Without the path, when a user says "save this image to the project" or "copy this screenshot into docs/", the model has no way to fulfill the request — it can describe the image but not access the file. The bytes are already on disk; the model just needs to know where.

Existing PRs considered

UI Changes

None — this is a server-only change to the message sent to the provider.

Test plan

  • Existing ClaudeAdapter.test.ts image embedding test updated to expect the path annotation
  • All 76 attachment-related tests pass (ClaudeAdapter: 52, CodexAdapter: 19, attachmentStore: 5)
  • Full typecheck passes
  • No surface overlap with any other open PR

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes (N/A — server-only)
  • I included a video for animation/interaction changes (N/A)

🤖 Generated with Claude Code


Note

Medium Risk
Changes provider prompt payloads and explicitly exposes server filesystem paths to models, which can affect prompting behavior and has security/privacy implications if paths are sensitive.

Overview
Provider models now receive filesystem path annotations for persisted image attachments, enabling them to reference/copy the saved files rather than only seeing base64 image data.

ClaudeAdapter appends a trailing { type: "text" } content block after each image with [Attached image "<name>" is saved at: <path>], and CodexAdapter augments the turn’s input text with the same annotations (newline-separated) while leaving the existing attachments payload intact. Tests were updated to assert the new Claude message shape.

Reviewed by Cursor Bugbot for commit f8201b2. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Expose persisted image attachment paths to Claude and Codex provider models

  • In ClaudeAdapter.ts, buildUserMessageEffect appends a text content block after each image block with the format [Attached image "<name>" is saved at: <path>].
  • In CodexAdapter.ts, a new resolveAttachmentPaths helper builds the same annotations and appends them to the text input sent to the Codex server; if no input text exists, the annotations are used alone.
  • Behavioral Change: Provider models now receive on-disk file paths for image attachments in every user turn, which was not previously included in the message content.

Macroscope summarized f8201b2.

When users attach images in chat, the server already persists them to
disk but only sends base64 data to the model. The model can see the
image but has no filesystem path to reference it, making it impossible
to copy or move the file into the project workspace.
Add a text annotation alongside each image content block that includes
the on-disk path where the attachment was saved. This lets the model
access the persisted file directly (e.g. to copy it into the project).
Applied to both ClaudeAdapter (inline content block) and CodexAdapter
(appended to input text).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Apr 16, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 7065b75f-cacf-41dc-817e-a3ba682827c2

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actionsgithub-actionsBot added size:S 10-29 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Apr 16, 2026

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 9374731. Configure here.

Comment threadapps/server/src/provider/Layers/CodexAdapter.ts Outdated
Comment threadapps/server/src/provider/Layers/CodexAdapter.ts
When a user attaches images without typing any text, input.input is
undefined. The previous ternary silently dropped path annotations in
this case. Restructure the conditional to emit annotations alone when
no user text is present.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actionsgithub-actionsBot added size:M 30-99 changed lines (additions + deletions). and removed size:S 10-29 changed lines (additions + deletions). labels Apr 16, 2026
@macroscopeapp

macroscopeappBot commented Apr 17, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces a new capability by exposing filesystem paths to AI provider models, enabling them to access persisted image files on disk. While the diff is small, this changes what information gets sent to models at runtime and could have security implications worth a human reviewer's attention.

You can customize Macroscope's approvability policy. Learn more.

@juliusmarminge

Copy link
Copy Markdown
Member

if i send 5 images how does the model know which is which?

@roni-estein

Copy link
Copy Markdown
Author

Each image content block is immediately followed by its path annotation in the loop, so they're paired 1:1 by position and original filename. With 5 images the SDK message content array looks like:

[text: "user message"]
[image: base64 of photo1.png]
[text: [Attached image "photo1.png" is saved at: /path/to/thread-abc-uuid1.png]]
[image: base64 of diagram.png]
[text: [Attached image "diagram.png" is saved at: /path/to/thread-abc-uuid2.png]]
[image: base64 of screenshot.png]
[text: [Attached image "screenshot.png" is saved at: /path/to/thread-abc-uuid3.png]]
...

The model sees each image immediately next to its annotation — not batched at the end — so there's no ambiguity about which path belongs to which image.

roni-estein added a commit to roni-estein/t3code that referenced this pull request Apr 17, 2026
@juliusmarminge

Copy link
Copy Markdown
Member

Closing as part of backlog cleanup: this PR is stale, currently conflicting with main, has no approval, and has not had active remediation for 30+ days. If the change is still relevant, please rebase onto current main and request reopening or submit a focused current diff. Thank you for the contribution.

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

Labels

size:M30-99 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@roni-estein@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(server): expose persisted image attachment paths to provider models - #2094

Closed
roni-estein wants to merge 3 commits into
pingdotgg:mainfrom
roni-estein:fix/persist-image-attachment-paths
Closed

fix(server): expose persisted image attachment paths to provider models#2094
roni-estein wants to merge 3 commits into
pingdotgg:mainfrom
roni-estein:fix/persist-image-attachment-paths

Conversation

@roni-estein

@roni-esteinroni-estein commented Apr 16, 2026

Copy link
Copy Markdown

What Changed

When users attach images in chat, the server already persists them to disk in the attachments directory, but only sends base64-encoded data to the provider model. The model can see the image visually but has no filesystem path to reference, so it cannot copy or move the file into the project workspace when asked.

This PR adds a text annotation alongside each image that includes the on-disk path where the attachment was saved:

[Attached image "screenshot.png" is saved at: /path/to/attachments/thread-abc-uuid.png]

ClaudeAdapter — appends a { type: "text" } content block after each image block in the SDK message.

CodexAdapter — appends path annotations to the input text (since Codex uses a separate attachments array rather than inline content blocks).

Why

Without the path, when a user says "save this image to the project" or "copy this screenshot into docs/", the model has no way to fulfill the request — it can describe the image but not access the file. The bytes are already on disk; the model just needs to know where.

Existing PRs considered

UI Changes

None — this is a server-only change to the message sent to the provider.

Test plan

  • Existing ClaudeAdapter.test.ts image embedding test updated to expect the path annotation
  • All 76 attachment-related tests pass (ClaudeAdapter: 52, CodexAdapter: 19, attachmentStore: 5)
  • Full typecheck passes
  • No surface overlap with any other open PR

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes (N/A — server-only)
  • I included a video for animation/interaction changes (N/A)

🤖 Generated with Claude Code


Note

Medium Risk
Changes provider prompt payloads and explicitly exposes server filesystem paths to models, which can affect prompting behavior and has security/privacy implications if paths are sensitive.

Overview
Provider models now receive filesystem path annotations for persisted image attachments, enabling them to reference/copy the saved files rather than only seeing base64 image data.

ClaudeAdapter appends a trailing { type: "text" } content block after each image with [Attached image "<name>" is saved at: <path>], and CodexAdapter augments the turn’s input text with the same annotations (newline-separated) while leaving the existing attachments payload intact. Tests were updated to assert the new Claude message shape.

Reviewed by Cursor Bugbot for commit f8201b2. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Expose persisted image attachment paths to Claude and Codex provider models

  • In ClaudeAdapter.ts, buildUserMessageEffect appends a text content block after each image block with the format [Attached image "<name>" is saved at: <path>].
  • In CodexAdapter.ts, a new resolveAttachmentPaths helper builds the same annotations and appends them to the text input sent to the Codex server; if no input text exists, the annotations are used alone.
  • Behavioral Change: Provider models now receive on-disk file paths for image attachments in every user turn, which was not previously included in the message content.

Macroscope summarized f8201b2.

When users attach images in chat, the server already persists them to
disk but only sends base64 data to the model. The model can see the
image but has no filesystem path to reference it, making it impossible
to copy or move the file into the project workspace.
Add a text annotation alongside each image content block that includes
the on-disk path where the attachment was saved. This lets the model
access the persisted file directly (e.g. to copy it into the project).
Applied to both ClaudeAdapter (inline content block) and CodexAdapter
(appended to input text).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Apr 16, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 7065b75f-cacf-41dc-817e-a3ba682827c2

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actionsgithub-actionsBot added size:S 10-29 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Apr 16, 2026

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 9374731. Configure here.

Comment threadapps/server/src/provider/Layers/CodexAdapter.ts Outdated
Comment threadapps/server/src/provider/Layers/CodexAdapter.ts
When a user attaches images without typing any text, input.input is
undefined. The previous ternary silently dropped path annotations in
this case. Restructure the conditional to emit annotations alone when
no user text is present.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actionsgithub-actionsBot added size:M 30-99 changed lines (additions + deletions). and removed size:S 10-29 changed lines (additions + deletions). labels Apr 16, 2026
@macroscopeapp

macroscopeappBot commented Apr 17, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces a new capability by exposing filesystem paths to AI provider models, enabling them to access persisted image files on disk. While the diff is small, this changes what information gets sent to models at runtime and could have security implications worth a human reviewer's attention.

You can customize Macroscope's approvability policy. Learn more.

@juliusmarminge

Copy link
Copy Markdown
Member

if i send 5 images how does the model know which is which?

@roni-estein

Copy link
Copy Markdown
Author

Each image content block is immediately followed by its path annotation in the loop, so they're paired 1:1 by position and original filename. With 5 images the SDK message content array looks like:

[text: "user message"]
[image: base64 of photo1.png]
[text: [Attached image "photo1.png" is saved at: /path/to/thread-abc-uuid1.png]]
[image: base64 of diagram.png]
[text: [Attached image "diagram.png" is saved at: /path/to/thread-abc-uuid2.png]]
[image: base64 of screenshot.png]
[text: [Attached image "screenshot.png" is saved at: /path/to/thread-abc-uuid3.png]]
...

The model sees each image immediately next to its annotation — not batched at the end — so there's no ambiguity about which path belongs to which image.

roni-estein added a commit to roni-estein/t3code that referenced this pull request Apr 17, 2026
@juliusmarminge

Copy link
Copy Markdown
Member

Closing as part of backlog cleanup: this PR is stale, currently conflicting with main, has no approval, and has not had active remediation for 30+ days. If the change is still relevant, please rebase onto current main and request reopening or submit a focused current diff. Thank you for the contribution.

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

Labels

size:M30-99 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@roni-estein@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(server): expose persisted image attachment paths to provider models - #2094

Closed
roni-estein wants to merge 3 commits into
pingdotgg:mainfrom
roni-estein:fix/persist-image-attachment-paths
Closed

fix(server): expose persisted image attachment paths to provider models#2094
roni-estein wants to merge 3 commits into
pingdotgg:mainfrom
roni-estein:fix/persist-image-attachment-paths

Conversation

@roni-estein

@roni-esteinroni-estein commented Apr 16, 2026

Copy link
Copy Markdown

What Changed

When users attach images in chat, the server already persists them to disk in the attachments directory, but only sends base64-encoded data to the provider model. The model can see the image visually but has no filesystem path to reference, so it cannot copy or move the file into the project workspace when asked.

This PR adds a text annotation alongside each image that includes the on-disk path where the attachment was saved:

[Attached image "screenshot.png" is saved at: /path/to/attachments/thread-abc-uuid.png]

ClaudeAdapter — appends a { type: "text" } content block after each image block in the SDK message.

CodexAdapter — appends path annotations to the input text (since Codex uses a separate attachments array rather than inline content blocks).

Why

Without the path, when a user says "save this image to the project" or "copy this screenshot into docs/", the model has no way to fulfill the request — it can describe the image but not access the file. The bytes are already on disk; the model just needs to know where.

Existing PRs considered

UI Changes

None — this is a server-only change to the message sent to the provider.

Test plan

  • Existing ClaudeAdapter.test.ts image embedding test updated to expect the path annotation
  • All 76 attachment-related tests pass (ClaudeAdapter: 52, CodexAdapter: 19, attachmentStore: 5)
  • Full typecheck passes
  • No surface overlap with any other open PR

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes (N/A — server-only)
  • I included a video for animation/interaction changes (N/A)

🤖 Generated with Claude Code


Note

Medium Risk
Changes provider prompt payloads and explicitly exposes server filesystem paths to models, which can affect prompting behavior and has security/privacy implications if paths are sensitive.

Overview
Provider models now receive filesystem path annotations for persisted image attachments, enabling them to reference/copy the saved files rather than only seeing base64 image data.

ClaudeAdapter appends a trailing { type: "text" } content block after each image with [Attached image "<name>" is saved at: <path>], and CodexAdapter augments the turn’s input text with the same annotations (newline-separated) while leaving the existing attachments payload intact. Tests were updated to assert the new Claude message shape.

Reviewed by Cursor Bugbot for commit f8201b2. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Expose persisted image attachment paths to Claude and Codex provider models

  • In ClaudeAdapter.ts, buildUserMessageEffect appends a text content block after each image block with the format [Attached image "<name>" is saved at: <path>].
  • In CodexAdapter.ts, a new resolveAttachmentPaths helper builds the same annotations and appends them to the text input sent to the Codex server; if no input text exists, the annotations are used alone.
  • Behavioral Change: Provider models now receive on-disk file paths for image attachments in every user turn, which was not previously included in the message content.

Macroscope summarized f8201b2.

When users attach images in chat, the server already persists them to
disk but only sends base64 data to the model. The model can see the
image but has no filesystem path to reference it, making it impossible
to copy or move the file into the project workspace.
Add a text annotation alongside each image content block that includes
the on-disk path where the attachment was saved. This lets the model
access the persisted file directly (e.g. to copy it into the project).
Applied to both ClaudeAdapter (inline content block) and CodexAdapter
(appended to input text).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Apr 16, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 7065b75f-cacf-41dc-817e-a3ba682827c2

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actionsgithub-actionsBot added size:S 10-29 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Apr 16, 2026

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 9374731. Configure here.

Comment threadapps/server/src/provider/Layers/CodexAdapter.ts Outdated
Comment threadapps/server/src/provider/Layers/CodexAdapter.ts
When a user attaches images without typing any text, input.input is
undefined. The previous ternary silently dropped path annotations in
this case. Restructure the conditional to emit annotations alone when
no user text is present.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actionsgithub-actionsBot added size:M 30-99 changed lines (additions + deletions). and removed size:S 10-29 changed lines (additions + deletions). labels Apr 16, 2026
@macroscopeapp

macroscopeappBot commented Apr 17, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces a new capability by exposing filesystem paths to AI provider models, enabling them to access persisted image files on disk. While the diff is small, this changes what information gets sent to models at runtime and could have security implications worth a human reviewer's attention.

You can customize Macroscope's approvability policy. Learn more.

@juliusmarminge

Copy link
Copy Markdown
Member

if i send 5 images how does the model know which is which?

@roni-estein

Copy link
Copy Markdown
Author

Each image content block is immediately followed by its path annotation in the loop, so they're paired 1:1 by position and original filename. With 5 images the SDK message content array looks like:

[text: "user message"]
[image: base64 of photo1.png]
[text: [Attached image "photo1.png" is saved at: /path/to/thread-abc-uuid1.png]]
[image: base64 of diagram.png]
[text: [Attached image "diagram.png" is saved at: /path/to/thread-abc-uuid2.png]]
[image: base64 of screenshot.png]
[text: [Attached image "screenshot.png" is saved at: /path/to/thread-abc-uuid3.png]]
...

The model sees each image immediately next to its annotation — not batched at the end — so there's no ambiguity about which path belongs to which image.

roni-estein added a commit to roni-estein/t3code that referenced this pull request Apr 17, 2026
@juliusmarminge

Copy link
Copy Markdown
Member

Closing as part of backlog cleanup: this PR is stale, currently conflicting with main, has no approval, and has not had active remediation for 30+ days. If the change is still relevant, please rebase onto current main and request reopening or submit a focused current diff. Thank you for the contribution.

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

Labels

size:M30-99 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@roni-estein@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(server): expose persisted image attachment paths to provider models - #2094

Closed
roni-estein wants to merge 3 commits into
pingdotgg:mainfrom
roni-estein:fix/persist-image-attachment-paths
Closed

fix(server): expose persisted image attachment paths to provider models#2094
roni-estein wants to merge 3 commits into
pingdotgg:mainfrom
roni-estein:fix/persist-image-attachment-paths

Conversation

@roni-estein

@roni-esteinroni-estein commented Apr 16, 2026

Copy link
Copy Markdown

What Changed

When users attach images in chat, the server already persists them to disk in the attachments directory, but only sends base64-encoded data to the provider model. The model can see the image visually but has no filesystem path to reference, so it cannot copy or move the file into the project workspace when asked.

This PR adds a text annotation alongside each image that includes the on-disk path where the attachment was saved:

[Attached image "screenshot.png" is saved at: /path/to/attachments/thread-abc-uuid.png]

ClaudeAdapter — appends a { type: "text" } content block after each image block in the SDK message.

CodexAdapter — appends path annotations to the input text (since Codex uses a separate attachments array rather than inline content blocks).

Why

Without the path, when a user says "save this image to the project" or "copy this screenshot into docs/", the model has no way to fulfill the request — it can describe the image but not access the file. The bytes are already on disk; the model just needs to know where.

Existing PRs considered

UI Changes

None — this is a server-only change to the message sent to the provider.

Test plan

  • Existing ClaudeAdapter.test.ts image embedding test updated to expect the path annotation
  • All 76 attachment-related tests pass (ClaudeAdapter: 52, CodexAdapter: 19, attachmentStore: 5)
  • Full typecheck passes
  • No surface overlap with any other open PR

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes (N/A — server-only)
  • I included a video for animation/interaction changes (N/A)

🤖 Generated with Claude Code


Note

Medium Risk
Changes provider prompt payloads and explicitly exposes server filesystem paths to models, which can affect prompting behavior and has security/privacy implications if paths are sensitive.

Overview
Provider models now receive filesystem path annotations for persisted image attachments, enabling them to reference/copy the saved files rather than only seeing base64 image data.

ClaudeAdapter appends a trailing { type: "text" } content block after each image with [Attached image "<name>" is saved at: <path>], and CodexAdapter augments the turn’s input text with the same annotations (newline-separated) while leaving the existing attachments payload intact. Tests were updated to assert the new Claude message shape.

Reviewed by Cursor Bugbot for commit f8201b2. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Expose persisted image attachment paths to Claude and Codex provider models

  • In ClaudeAdapter.ts, buildUserMessageEffect appends a text content block after each image block with the format [Attached image "<name>" is saved at: <path>].
  • In CodexAdapter.ts, a new resolveAttachmentPaths helper builds the same annotations and appends them to the text input sent to the Codex server; if no input text exists, the annotations are used alone.
  • Behavioral Change: Provider models now receive on-disk file paths for image attachments in every user turn, which was not previously included in the message content.

Macroscope summarized f8201b2.

When users attach images in chat, the server already persists them to
disk but only sends base64 data to the model. The model can see the
image but has no filesystem path to reference it, making it impossible
to copy or move the file into the project workspace.
Add a text annotation alongside each image content block that includes
the on-disk path where the attachment was saved. This lets the model
access the persisted file directly (e.g. to copy it into the project).
Applied to both ClaudeAdapter (inline content block) and CodexAdapter
(appended to input text).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Apr 16, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 7065b75f-cacf-41dc-817e-a3ba682827c2

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actionsgithub-actionsBot added size:S 10-29 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Apr 16, 2026

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 9374731. Configure here.

Comment threadapps/server/src/provider/Layers/CodexAdapter.ts Outdated
Comment threadapps/server/src/provider/Layers/CodexAdapter.ts
When a user attaches images without typing any text, input.input is
undefined. The previous ternary silently dropped path annotations in
this case. Restructure the conditional to emit annotations alone when
no user text is present.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actionsgithub-actionsBot added size:M 30-99 changed lines (additions + deletions). and removed size:S 10-29 changed lines (additions + deletions). labels Apr 16, 2026
@macroscopeapp

macroscopeappBot commented Apr 17, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces a new capability by exposing filesystem paths to AI provider models, enabling them to access persisted image files on disk. While the diff is small, this changes what information gets sent to models at runtime and could have security implications worth a human reviewer's attention.

You can customize Macroscope's approvability policy. Learn more.

@juliusmarminge

Copy link
Copy Markdown
Member

if i send 5 images how does the model know which is which?

@roni-estein

Copy link
Copy Markdown
Author

Each image content block is immediately followed by its path annotation in the loop, so they're paired 1:1 by position and original filename. With 5 images the SDK message content array looks like:

[text: "user message"]
[image: base64 of photo1.png]
[text: [Attached image "photo1.png" is saved at: /path/to/thread-abc-uuid1.png]]
[image: base64 of diagram.png]
[text: [Attached image "diagram.png" is saved at: /path/to/thread-abc-uuid2.png]]
[image: base64 of screenshot.png]
[text: [Attached image "screenshot.png" is saved at: /path/to/thread-abc-uuid3.png]]
...

The model sees each image immediately next to its annotation — not batched at the end — so there's no ambiguity about which path belongs to which image.

roni-estein added a commit to roni-estein/t3code that referenced this pull request Apr 17, 2026
@juliusmarminge

Copy link
Copy Markdown
Member

Closing as part of backlog cleanup: this PR is stale, currently conflicting with main, has no approval, and has not had active remediation for 30+ days. If the change is still relevant, please rebase onto current main and request reopening or submit a focused current diff. Thank you for the contribution.

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

Labels

size:M30-99 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@roni-estein@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(server): expose persisted image attachment paths to provider models - #2094

Closed
roni-estein wants to merge 3 commits into
pingdotgg:mainfrom
roni-estein:fix/persist-image-attachment-paths
Closed

fix(server): expose persisted image attachment paths to provider models#2094
roni-estein wants to merge 3 commits into
pingdotgg:mainfrom
roni-estein:fix/persist-image-attachment-paths

Conversation

@roni-estein

@roni-esteinroni-estein commented Apr 16, 2026

Copy link
Copy Markdown

What Changed

When users attach images in chat, the server already persists them to disk in the attachments directory, but only sends base64-encoded data to the provider model. The model can see the image visually but has no filesystem path to reference, so it cannot copy or move the file into the project workspace when asked.

This PR adds a text annotation alongside each image that includes the on-disk path where the attachment was saved:

[Attached image "screenshot.png" is saved at: /path/to/attachments/thread-abc-uuid.png]

ClaudeAdapter — appends a { type: "text" } content block after each image block in the SDK message.

CodexAdapter — appends path annotations to the input text (since Codex uses a separate attachments array rather than inline content blocks).

Why

Without the path, when a user says "save this image to the project" or "copy this screenshot into docs/", the model has no way to fulfill the request — it can describe the image but not access the file. The bytes are already on disk; the model just needs to know where.

Existing PRs considered

UI Changes

None — this is a server-only change to the message sent to the provider.

Test plan

  • Existing ClaudeAdapter.test.ts image embedding test updated to expect the path annotation
  • All 76 attachment-related tests pass (ClaudeAdapter: 52, CodexAdapter: 19, attachmentStore: 5)
  • Full typecheck passes
  • No surface overlap with any other open PR

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes (N/A — server-only)
  • I included a video for animation/interaction changes (N/A)

🤖 Generated with Claude Code


Note

Medium Risk
Changes provider prompt payloads and explicitly exposes server filesystem paths to models, which can affect prompting behavior and has security/privacy implications if paths are sensitive.

Overview
Provider models now receive filesystem path annotations for persisted image attachments, enabling them to reference/copy the saved files rather than only seeing base64 image data.

ClaudeAdapter appends a trailing { type: "text" } content block after each image with [Attached image "<name>" is saved at: <path>], and CodexAdapter augments the turn’s input text with the same annotations (newline-separated) while leaving the existing attachments payload intact. Tests were updated to assert the new Claude message shape.

Reviewed by Cursor Bugbot for commit f8201b2. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Expose persisted image attachment paths to Claude and Codex provider models

  • In ClaudeAdapter.ts, buildUserMessageEffect appends a text content block after each image block with the format [Attached image "<name>" is saved at: <path>].
  • In CodexAdapter.ts, a new resolveAttachmentPaths helper builds the same annotations and appends them to the text input sent to the Codex server; if no input text exists, the annotations are used alone.
  • Behavioral Change: Provider models now receive on-disk file paths for image attachments in every user turn, which was not previously included in the message content.

Macroscope summarized f8201b2.

When users attach images in chat, the server already persists them to
disk but only sends base64 data to the model. The model can see the
image but has no filesystem path to reference it, making it impossible
to copy or move the file into the project workspace.
Add a text annotation alongside each image content block that includes
the on-disk path where the attachment was saved. This lets the model
access the persisted file directly (e.g. to copy it into the project).
Applied to both ClaudeAdapter (inline content block) and CodexAdapter
(appended to input text).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Apr 16, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 7065b75f-cacf-41dc-817e-a3ba682827c2

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actionsgithub-actionsBot added size:S 10-29 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Apr 16, 2026

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 9374731. Configure here.

Comment threadapps/server/src/provider/Layers/CodexAdapter.ts Outdated
Comment threadapps/server/src/provider/Layers/CodexAdapter.ts
When a user attaches images without typing any text, input.input is
undefined. The previous ternary silently dropped path annotations in
this case. Restructure the conditional to emit annotations alone when
no user text is present.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actionsgithub-actionsBot added size:M 30-99 changed lines (additions + deletions). and removed size:S 10-29 changed lines (additions + deletions). labels Apr 16, 2026
@macroscopeapp

macroscopeappBot commented Apr 17, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces a new capability by exposing filesystem paths to AI provider models, enabling them to access persisted image files on disk. While the diff is small, this changes what information gets sent to models at runtime and could have security implications worth a human reviewer's attention.

You can customize Macroscope's approvability policy. Learn more.

@juliusmarminge

Copy link
Copy Markdown
Member

if i send 5 images how does the model know which is which?

@roni-estein

Copy link
Copy Markdown
Author

Each image content block is immediately followed by its path annotation in the loop, so they're paired 1:1 by position and original filename. With 5 images the SDK message content array looks like:

[text: "user message"]
[image: base64 of photo1.png]
[text: [Attached image "photo1.png" is saved at: /path/to/thread-abc-uuid1.png]]
[image: base64 of diagram.png]
[text: [Attached image "diagram.png" is saved at: /path/to/thread-abc-uuid2.png]]
[image: base64 of screenshot.png]
[text: [Attached image "screenshot.png" is saved at: /path/to/thread-abc-uuid3.png]]
...

The model sees each image immediately next to its annotation — not batched at the end — so there's no ambiguity about which path belongs to which image.

roni-estein added a commit to roni-estein/t3code that referenced this pull request Apr 17, 2026
@juliusmarminge

Copy link
Copy Markdown
Member

Closing as part of backlog cleanup: this PR is stale, currently conflicting with main, has no approval, and has not had active remediation for 30+ days. If the change is still relevant, please rebase onto current main and request reopening or submit a focused current diff. Thank you for the contribution.

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

Labels

size:M30-99 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@roni-estein@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(server): expose persisted image attachment paths to provider models - #2094

Closed
roni-estein wants to merge 3 commits into
pingdotgg:mainfrom
roni-estein:fix/persist-image-attachment-paths
Closed

fix(server): expose persisted image attachment paths to provider models#2094
roni-estein wants to merge 3 commits into
pingdotgg:mainfrom
roni-estein:fix/persist-image-attachment-paths

Conversation

@roni-estein

@roni-esteinroni-estein commented Apr 16, 2026

Copy link
Copy Markdown

What Changed

When users attach images in chat, the server already persists them to disk in the attachments directory, but only sends base64-encoded data to the provider model. The model can see the image visually but has no filesystem path to reference, so it cannot copy or move the file into the project workspace when asked.

This PR adds a text annotation alongside each image that includes the on-disk path where the attachment was saved:

[Attached image "screenshot.png" is saved at: /path/to/attachments/thread-abc-uuid.png]

ClaudeAdapter — appends a { type: "text" } content block after each image block in the SDK message.

CodexAdapter — appends path annotations to the input text (since Codex uses a separate attachments array rather than inline content blocks).

Why

Without the path, when a user says "save this image to the project" or "copy this screenshot into docs/", the model has no way to fulfill the request — it can describe the image but not access the file. The bytes are already on disk; the model just needs to know where.

Existing PRs considered

UI Changes

None — this is a server-only change to the message sent to the provider.

Test plan

  • Existing ClaudeAdapter.test.ts image embedding test updated to expect the path annotation
  • All 76 attachment-related tests pass (ClaudeAdapter: 52, CodexAdapter: 19, attachmentStore: 5)
  • Full typecheck passes
  • No surface overlap with any other open PR

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes (N/A — server-only)
  • I included a video for animation/interaction changes (N/A)

🤖 Generated with Claude Code


Note

Medium Risk
Changes provider prompt payloads and explicitly exposes server filesystem paths to models, which can affect prompting behavior and has security/privacy implications if paths are sensitive.

Overview
Provider models now receive filesystem path annotations for persisted image attachments, enabling them to reference/copy the saved files rather than only seeing base64 image data.

ClaudeAdapter appends a trailing { type: "text" } content block after each image with [Attached image "<name>" is saved at: <path>], and CodexAdapter augments the turn’s input text with the same annotations (newline-separated) while leaving the existing attachments payload intact. Tests were updated to assert the new Claude message shape.

Reviewed by Cursor Bugbot for commit f8201b2. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Expose persisted image attachment paths to Claude and Codex provider models

  • In ClaudeAdapter.ts, buildUserMessageEffect appends a text content block after each image block with the format [Attached image "<name>" is saved at: <path>].
  • In CodexAdapter.ts, a new resolveAttachmentPaths helper builds the same annotations and appends them to the text input sent to the Codex server; if no input text exists, the annotations are used alone.
  • Behavioral Change: Provider models now receive on-disk file paths for image attachments in every user turn, which was not previously included in the message content.

Macroscope summarized f8201b2.

When users attach images in chat, the server already persists them to
disk but only sends base64 data to the model. The model can see the
image but has no filesystem path to reference it, making it impossible
to copy or move the file into the project workspace.
Add a text annotation alongside each image content block that includes
the on-disk path where the attachment was saved. This lets the model
access the persisted file directly (e.g. to copy it into the project).
Applied to both ClaudeAdapter (inline content block) and CodexAdapter
(appended to input text).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Apr 16, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 7065b75f-cacf-41dc-817e-a3ba682827c2

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actionsgithub-actionsBot added size:S 10-29 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Apr 16, 2026

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 9374731. Configure here.

Comment threadapps/server/src/provider/Layers/CodexAdapter.ts Outdated
Comment threadapps/server/src/provider/Layers/CodexAdapter.ts
When a user attaches images without typing any text, input.input is
undefined. The previous ternary silently dropped path annotations in
this case. Restructure the conditional to emit annotations alone when
no user text is present.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actionsgithub-actionsBot added size:M 30-99 changed lines (additions + deletions). and removed size:S 10-29 changed lines (additions + deletions). labels Apr 16, 2026
@macroscopeapp

macroscopeappBot commented Apr 17, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces a new capability by exposing filesystem paths to AI provider models, enabling them to access persisted image files on disk. While the diff is small, this changes what information gets sent to models at runtime and could have security implications worth a human reviewer's attention.

You can customize Macroscope's approvability policy. Learn more.

@juliusmarminge

Copy link
Copy Markdown
Member

if i send 5 images how does the model know which is which?

@roni-estein

Copy link
Copy Markdown
Author

Each image content block is immediately followed by its path annotation in the loop, so they're paired 1:1 by position and original filename. With 5 images the SDK message content array looks like:

[text: "user message"]
[image: base64 of photo1.png]
[text: [Attached image "photo1.png" is saved at: /path/to/thread-abc-uuid1.png]]
[image: base64 of diagram.png]
[text: [Attached image "diagram.png" is saved at: /path/to/thread-abc-uuid2.png]]
[image: base64 of screenshot.png]
[text: [Attached image "screenshot.png" is saved at: /path/to/thread-abc-uuid3.png]]
...

The model sees each image immediately next to its annotation — not batched at the end — so there's no ambiguity about which path belongs to which image.

roni-estein added a commit to roni-estein/t3code that referenced this pull request Apr 17, 2026
@juliusmarminge

Copy link
Copy Markdown
Member

Closing as part of backlog cleanup: this PR is stale, currently conflicting with main, has no approval, and has not had active remediation for 30+ days. If the change is still relevant, please rebase onto current main and request reopening or submit a focused current diff. Thank you for the contribution.

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

Labels

size:M30-99 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@roni-estein@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(server): expose persisted image attachment paths to provider models - #2094

Closed
roni-estein wants to merge 3 commits into
pingdotgg:mainfrom
roni-estein:fix/persist-image-attachment-paths
Closed

fix(server): expose persisted image attachment paths to provider models#2094
roni-estein wants to merge 3 commits into
pingdotgg:mainfrom
roni-estein:fix/persist-image-attachment-paths

Conversation

@roni-estein

@roni-esteinroni-estein commented Apr 16, 2026

Copy link
Copy Markdown

What Changed

When users attach images in chat, the server already persists them to disk in the attachments directory, but only sends base64-encoded data to the provider model. The model can see the image visually but has no filesystem path to reference, so it cannot copy or move the file into the project workspace when asked.

This PR adds a text annotation alongside each image that includes the on-disk path where the attachment was saved:

[Attached image "screenshot.png" is saved at: /path/to/attachments/thread-abc-uuid.png]

ClaudeAdapter — appends a { type: "text" } content block after each image block in the SDK message.

CodexAdapter — appends path annotations to the input text (since Codex uses a separate attachments array rather than inline content blocks).

Why

Without the path, when a user says "save this image to the project" or "copy this screenshot into docs/", the model has no way to fulfill the request — it can describe the image but not access the file. The bytes are already on disk; the model just needs to know where.

Existing PRs considered

UI Changes

None — this is a server-only change to the message sent to the provider.

Test plan

  • Existing ClaudeAdapter.test.ts image embedding test updated to expect the path annotation
  • All 76 attachment-related tests pass (ClaudeAdapter: 52, CodexAdapter: 19, attachmentStore: 5)
  • Full typecheck passes
  • No surface overlap with any other open PR

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes (N/A — server-only)
  • I included a video for animation/interaction changes (N/A)

🤖 Generated with Claude Code


Note

Medium Risk
Changes provider prompt payloads and explicitly exposes server filesystem paths to models, which can affect prompting behavior and has security/privacy implications if paths are sensitive.

Overview
Provider models now receive filesystem path annotations for persisted image attachments, enabling them to reference/copy the saved files rather than only seeing base64 image data.

ClaudeAdapter appends a trailing { type: "text" } content block after each image with [Attached image "<name>" is saved at: <path>], and CodexAdapter augments the turn’s input text with the same annotations (newline-separated) while leaving the existing attachments payload intact. Tests were updated to assert the new Claude message shape.

Reviewed by Cursor Bugbot for commit f8201b2. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Expose persisted image attachment paths to Claude and Codex provider models

  • In ClaudeAdapter.ts, buildUserMessageEffect appends a text content block after each image block with the format [Attached image "<name>" is saved at: <path>].
  • In CodexAdapter.ts, a new resolveAttachmentPaths helper builds the same annotations and appends them to the text input sent to the Codex server; if no input text exists, the annotations are used alone.
  • Behavioral Change: Provider models now receive on-disk file paths for image attachments in every user turn, which was not previously included in the message content.

Macroscope summarized f8201b2.

When users attach images in chat, the server already persists them to
disk but only sends base64 data to the model. The model can see the
image but has no filesystem path to reference it, making it impossible
to copy or move the file into the project workspace.
Add a text annotation alongside each image content block that includes
the on-disk path where the attachment was saved. This lets the model
access the persisted file directly (e.g. to copy it into the project).
Applied to both ClaudeAdapter (inline content block) and CodexAdapter
(appended to input text).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Apr 16, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 7065b75f-cacf-41dc-817e-a3ba682827c2

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actionsgithub-actionsBot added size:S 10-29 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Apr 16, 2026

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 9374731. Configure here.

Comment threadapps/server/src/provider/Layers/CodexAdapter.ts Outdated
Comment threadapps/server/src/provider/Layers/CodexAdapter.ts
When a user attaches images without typing any text, input.input is
undefined. The previous ternary silently dropped path annotations in
this case. Restructure the conditional to emit annotations alone when
no user text is present.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actionsgithub-actionsBot added size:M 30-99 changed lines (additions + deletions). and removed size:S 10-29 changed lines (additions + deletions). labels Apr 16, 2026
@macroscopeapp

macroscopeappBot commented Apr 17, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces a new capability by exposing filesystem paths to AI provider models, enabling them to access persisted image files on disk. While the diff is small, this changes what information gets sent to models at runtime and could have security implications worth a human reviewer's attention.

You can customize Macroscope's approvability policy. Learn more.

@juliusmarminge

Copy link
Copy Markdown
Member

if i send 5 images how does the model know which is which?

@roni-estein

Copy link
Copy Markdown
Author

Each image content block is immediately followed by its path annotation in the loop, so they're paired 1:1 by position and original filename. With 5 images the SDK message content array looks like:

[text: "user message"]
[image: base64 of photo1.png]
[text: [Attached image "photo1.png" is saved at: /path/to/thread-abc-uuid1.png]]
[image: base64 of diagram.png]
[text: [Attached image "diagram.png" is saved at: /path/to/thread-abc-uuid2.png]]
[image: base64 of screenshot.png]
[text: [Attached image "screenshot.png" is saved at: /path/to/thread-abc-uuid3.png]]
...

The model sees each image immediately next to its annotation — not batched at the end — so there's no ambiguity about which path belongs to which image.

roni-estein added a commit to roni-estein/t3code that referenced this pull request Apr 17, 2026
@juliusmarminge

Copy link
Copy Markdown
Member

Closing as part of backlog cleanup: this PR is stale, currently conflicting with main, has no approval, and has not had active remediation for 30+ days. If the change is still relevant, please rebase onto current main and request reopening or submit a focused current diff. Thank you for the contribution.

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

Labels

size:M30-99 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@roni-estein@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(server): expose persisted image attachment paths to provider models - #2094

Closed
roni-estein wants to merge 3 commits into
pingdotgg:mainfrom
roni-estein:fix/persist-image-attachment-paths
Closed

fix(server): expose persisted image attachment paths to provider models#2094
roni-estein wants to merge 3 commits into
pingdotgg:mainfrom
roni-estein:fix/persist-image-attachment-paths

Conversation

@roni-estein

@roni-esteinroni-estein commented Apr 16, 2026

Copy link
Copy Markdown

What Changed

When users attach images in chat, the server already persists them to disk in the attachments directory, but only sends base64-encoded data to the provider model. The model can see the image visually but has no filesystem path to reference, so it cannot copy or move the file into the project workspace when asked.

This PR adds a text annotation alongside each image that includes the on-disk path where the attachment was saved:

[Attached image "screenshot.png" is saved at: /path/to/attachments/thread-abc-uuid.png]

ClaudeAdapter — appends a { type: "text" } content block after each image block in the SDK message.

CodexAdapter — appends path annotations to the input text (since Codex uses a separate attachments array rather than inline content blocks).

Why

Without the path, when a user says "save this image to the project" or "copy this screenshot into docs/", the model has no way to fulfill the request — it can describe the image but not access the file. The bytes are already on disk; the model just needs to know where.

Existing PRs considered

UI Changes

None — this is a server-only change to the message sent to the provider.

Test plan

  • Existing ClaudeAdapter.test.ts image embedding test updated to expect the path annotation
  • All 76 attachment-related tests pass (ClaudeAdapter: 52, CodexAdapter: 19, attachmentStore: 5)
  • Full typecheck passes
  • No surface overlap with any other open PR

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes (N/A — server-only)
  • I included a video for animation/interaction changes (N/A)

🤖 Generated with Claude Code


Note

Medium Risk
Changes provider prompt payloads and explicitly exposes server filesystem paths to models, which can affect prompting behavior and has security/privacy implications if paths are sensitive.

Overview
Provider models now receive filesystem path annotations for persisted image attachments, enabling them to reference/copy the saved files rather than only seeing base64 image data.

ClaudeAdapter appends a trailing { type: "text" } content block after each image with [Attached image "<name>" is saved at: <path>], and CodexAdapter augments the turn’s input text with the same annotations (newline-separated) while leaving the existing attachments payload intact. Tests were updated to assert the new Claude message shape.

Reviewed by Cursor Bugbot for commit f8201b2. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Expose persisted image attachment paths to Claude and Codex provider models

  • In ClaudeAdapter.ts, buildUserMessageEffect appends a text content block after each image block with the format [Attached image "<name>" is saved at: <path>].
  • In CodexAdapter.ts, a new resolveAttachmentPaths helper builds the same annotations and appends them to the text input sent to the Codex server; if no input text exists, the annotations are used alone.
  • Behavioral Change: Provider models now receive on-disk file paths for image attachments in every user turn, which was not previously included in the message content.

Macroscope summarized f8201b2.

When users attach images in chat, the server already persists them to
disk but only sends base64 data to the model. The model can see the
image but has no filesystem path to reference it, making it impossible
to copy or move the file into the project workspace.
Add a text annotation alongside each image content block that includes
the on-disk path where the attachment was saved. This lets the model
access the persisted file directly (e.g. to copy it into the project).
Applied to both ClaudeAdapter (inline content block) and CodexAdapter
(appended to input text).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Apr 16, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 7065b75f-cacf-41dc-817e-a3ba682827c2

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actionsgithub-actionsBot added size:S 10-29 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Apr 16, 2026

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 9374731. Configure here.

Comment threadapps/server/src/provider/Layers/CodexAdapter.ts Outdated
Comment threadapps/server/src/provider/Layers/CodexAdapter.ts
When a user attaches images without typing any text, input.input is
undefined. The previous ternary silently dropped path annotations in
this case. Restructure the conditional to emit annotations alone when
no user text is present.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actionsgithub-actionsBot added size:M 30-99 changed lines (additions + deletions). and removed size:S 10-29 changed lines (additions + deletions). labels Apr 16, 2026
@macroscopeapp

macroscopeappBot commented Apr 17, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces a new capability by exposing filesystem paths to AI provider models, enabling them to access persisted image files on disk. While the diff is small, this changes what information gets sent to models at runtime and could have security implications worth a human reviewer's attention.

You can customize Macroscope's approvability policy. Learn more.

@juliusmarminge

Copy link
Copy Markdown
Member

if i send 5 images how does the model know which is which?

@roni-estein

Copy link
Copy Markdown
Author

Each image content block is immediately followed by its path annotation in the loop, so they're paired 1:1 by position and original filename. With 5 images the SDK message content array looks like:

[text: "user message"]
[image: base64 of photo1.png]
[text: [Attached image "photo1.png" is saved at: /path/to/thread-abc-uuid1.png]]
[image: base64 of diagram.png]
[text: [Attached image "diagram.png" is saved at: /path/to/thread-abc-uuid2.png]]
[image: base64 of screenshot.png]
[text: [Attached image "screenshot.png" is saved at: /path/to/thread-abc-uuid3.png]]
...

The model sees each image immediately next to its annotation — not batched at the end — so there's no ambiguity about which path belongs to which image.

roni-estein added a commit to roni-estein/t3code that referenced this pull request Apr 17, 2026
@juliusmarminge

Copy link
Copy Markdown
Member

Closing as part of backlog cleanup: this PR is stale, currently conflicting with main, has no approval, and has not had active remediation for 30+ days. If the change is still relevant, please rebase onto current main and request reopening or submit a focused current diff. Thank you for the contribution.

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

Labels

size:M30-99 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@roni-estein@juliusmarminge