Skip to content

Studio: Stop Deep Research deleting links from code in its report - #10814

Open
NilayYadav wants to merge 6 commits into
unslothai:mainfrom
NilayYadav:research-code-urls
Open

Studio: Stop Deep Research deleting links from code in its report#10814
NilayYadav wants to merge 6 commits into
unslothai:mainfrom
NilayYadav:research-code-urls

Conversation

@NilayYadav

Copy link
Copy Markdown
Collaborator

Deep Research removed web addresses from the code and commands in its reports. A git clone command lost its GitHub link, pip install lost its --index-url address, and base_url="http://localhost:8888/v1" became base_url=". Code like x.shape[1] was also turned into a link to a source. The broken text was saved as the report and shown in the chat, so copied commands did not work.

This happened because the step that removes unverified links and numbered citations ran over the whole report, including code. Now code blocks and inline code are set aside during that step and put back exactly as written. A Sources heading inside a code block also no longer cuts off the rest of the report.

Links and citations in normal text work as before. Tested with a full Deep Research run. Before, the saved report had the broken commands above. After, every command and code line was saved unchanged.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 11, 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-09-11T22:36:31.409941Z a780408 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 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: 338407365f

ℹ️ 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".

Comment on lines +84 to +85
if fence is None or (fence.group(1)[0] == "`" and "`" in content[fence.end() :]):
pieces.append(_INLINE_CODE.sub(lambda match: mask(match.group(0)), line))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve indented Markdown code blocks

When the model emits a valid four-space- or tab-indented code block instead of a fence, this branch treats every line as prose. For example, an indented curl https://private.example/api becomes just curl, while print(x[1]) can have [1] rewritten as a citation, so the copied code remains corrupted despite this fix. Detect and mask indented code blocks as well.

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.

Fixed in 5c7506e: indented and list code blocks are masked from markdown-it block maps. Covered by test_report_preserves_indented_and_list_code.

Comment on lines +80 to +85
for line in text.splitlines(keepends = True):
content = line.rstrip("\r\n")
fence = _MARKDOWN_FENCE.match(content)
if opening is None:
if fence is None or (fence.group(1)[0] == "`" and "`" in content[fence.end() :]):
pieces.append(_INLINE_CODE.sub(lambda match: mask(match.group(0)), line))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Mask multiline inline code spans as a unit

Markdown code spans may contain line endings, but applying _INLINE_CODE independently to each split line means neither delimiter is matched. With a span such as Use \curl\nhttps://private.example/api` now`, the URL is still removed from the stored report. Scan inline spans across the complete non-fenced text rather than one line at a time.

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.

Fixed in 5c7506e: code spans are parsed over the whole inline block, so multiline spans are masked as one unit. Covered by test_multiline_inline_code_keeps_urls_and_brackets.

Comment on lines +84 to +85
if fence is None or (fence.group(1)[0] == "`" and "`" in content[fence.end() :]):
pieces.append(_INLINE_CODE.sub(lambda match: mask(match.group(0)), line))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Do not mask backslash-escaped backticks

When prose uses escaped backticks to display literal backticks, such as \https://nope.example\``, _INLINE_CODE still recognizes the pair because it ignores backslash escaping. _mask_code consequently restores the unsupported URL verbatim instead of letting _RAW_URL remove it, allowing ordinary rendered prose to bypass gathered-source validation.

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.

Fixed in 5c7506e: spans come from the CommonMark backtick rule, which honours backslash escapes. Covered by test_literal_escaped_backticks_do_not_hide_prose_urls.

Comment on lines +100 to +101
if opening is not None:
pieces.append(mask(text[opening:]))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge End nested fences when their Markdown container ends

When an unclosed fenced block is nested in a block quote or list, Markdown ends that block when the containing quote/list ends, but opening remains set here until a matching fence or EOF. Consequently, ordinary following prose such as Outside https://nope.example and [1] is masked and restored unchanged, allowing unsupported URLs and citations to bypass validation. Track the opening container and stop masking when it ends.

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.

Fixed in 5c7506e: a fence now ends where markdown-it ends its container. Covered by test_unclosed_quote_fence_stops_at_the_quote_boundary.

source_urls = list(source_by_url)
placeholders: dict[str, str] = {}

report = _mask_code(report, placeholders)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve code through document-citation validation

Masking code only inside _validate_report_sources does not preserve it in the delivered report because research_runs.py immediately passes the restored result to _validate_report_document_sources. That validator still removes document-shaped text inside recognized code; for example, pattern = "[Document: generated]" in a fenced Python block becomes pattern = "". Keep code masked through both validators or make the document validator code-aware.

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.

Fixed in 5c7506e: _validate_report_document_sources masks code too. Covered by test_delivered_report_keeps_document_literals_in_code.

source_urls = list(source_by_url)
placeholders: dict[str, str] = {}

report = _mask_code(report, placeholders)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve code through document-citation validation

The production path immediately passes this result to _validate_report_document_sources, after these placeholders have already been restored. Therefore even a correctly recognized fenced block is still changed when it contains code such as pattern = "[Document: generated]": the second validator removes that substring and stores pattern = "". Keep code masked through both validators or make document code in the document-citation validator too.

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.

Same as the item above; fixed in 5c7506e.

@unslothai unslothai deleted a comment from chatgpt-codex-connector Bot Sep 11, 2026
@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: 5ddaf72bef

ℹ️ 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".

Comment on lines +101 to +105
if token.type in {"fence", "code_block"}:
# Keep the following heading on its own line while the block is masked.
end = start + len(text[start:end].rstrip("\r\n"))
spans.append((start, end))
elif token.type == "inline":

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve HTML code and preformatted blocks

When a report uses valid HTML code markup rather than Markdown fences/backticks, these branches ignore the resulting html_block/html_inline content. For example, <pre><code>curl https://private.example/api</code></pre> is still rewritten to <pre><code>curl </code></pre>, and bracketed expressions inside <code> can still become citations. Mask <pre>/<code> contents as code too so saved and copied examples are not corrupted.

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 here. The synthesis prompt asks for Markdown, and HTML

/ content is rewritten the same way on main, so this PR does not introduce it. Masking raw HTML elements would also let arbitrary HTML skip link validation, so it needs its own change with bypass tests.

@unslothai unslothai deleted a comment from chatgpt-codex-connector Bot Sep 11, 2026
@NilayYadav

Copy link
Copy Markdown
Collaborator Author

@codex review

1 similar comment
@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: 5ddaf72bef

ℹ️ 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".

# Tokenize valid citations first so a "]" inside a filename ("budget [final].pdf") does not
# truncate them, then strip the invalid ones and restore the valid.
placeholders: dict[str, str] = {}
report = _mask_code(report, placeholders)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reuse the original code boundaries across validators

When _validate_report_sources removes an unsupported URL that was the only content on a line, reparsing here can change the Markdown structure before document validation. For example, Context\nhttps://nope.example\n [Document: hallucinated] initially contains one paragraph, but the production sequence in core/research_runs.py turns it into Context\n\n [Document: hallucinated]; this call then classifies the indented citation as a code block and masks it, allowing the unsupported document citation to survive. Preserve the code spans identified from the original draft across both validators rather than deriving new spans after URL rewriting.

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.

Fixed in 13ac9d4: the draft is masked once and both validators run on that masked text (_validate_report), so removing a URL-only line can no longer turn an indented citation into code. Covered by test_removed_url_line_does_not_turn_a_document_citation_into_code.

@NilayYadav

NilayYadav commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator Author

Review, GitHub Actions and Studio UI evidence

Pinned head: a7804082bfcce603024b17a0e6101af90a1bafe5, staged on fresh main bf87e2917c.

GitHub Actions A/B

Both runs use the same workflow, probe and pip install -r studio/backend/requirements/studio.txt (markdown-it-py 4.2.0). The only difference is studio/backend/core/research/citations.py. The probe sends a Deep Research report containing code through each side's production entry point: _validate_report at head, and the two validators in sequence on main.

  • Fix reverted, main's citations.py: expected-failing run. Only 1/10 code lines are kept; prose checks pass 5/5.

    Code in the report Saved report
    git clone https://github.com/unslothai/unsloth git clone [unslothai/unsloth](https://github.com/unslothai/unsloth)
    pip install torch --index-url https://download.pytorch.org/whl/cu121 pip install torch --index-url
    client = OpenAI(base_url="http://localhost:8888/v1", api_key="none") client = OpenAI(base_url=", api_key="none")
    print(x.shape[1]) print(x.shape[unslothai/unsloth](https://github.com/unslothai/unsloth))
    pattern = "[Document: generated]" pattern = ""
  • PR head: passing run. 10/10 code lines (fenced, indented and inline) are kept unchanged. Prose checks pass 5/5:

    • [1] and [2] become source links;
    • the unverified prose URL and [Document: missing.pdf] are removed;
    • the model's ## Sources list is dropped.

Studio UI, before and after

The run goes through the real ResearchSupervisor on two separate Studio installs: 8ee07d6, this PR's merge base with main, and the head a780408. The model is a saved custom connection served from the scene process. A ddgs stub, identical on both sides, supplies one fixed web source. Both sides receive the same report, which contains code.

Highlighted lines differ between the two installs: red on main, green with this PR. Unhighlighted lines are identical.

PR 10814 Studio report before/after, changed lines highlighted

Measured on the photographed Studio BEFORE 8ee07d6 (merge base) AFTER a780408 (head)
Code lines kept in the saved report 1/10 10/10
Code lines kept in the rendered chat 1/10 10/10
Run status and gathered sources completed, github.com/unslothai/unsloth same
Prose [1] linked, unverified prose URL removed, model ## Sources list removed yes yes

Both sides rendered 3 code blocks, and neither Studio log contains a traceback.

Review fixes pushed

  1. 5ddaf72: studio extra. markdown-it-py>=3.0 was added to studio.txt but not to the studio extra in pyproject.toml, which failed tests/studio/install/test_studio_extra_matches_requirements.py in Repo tests (CPU). It went from 2 failed to 6 passed.
  2. 5ddaf72: URL glued to inline code. _RAW_URL ran into the code placeholder, so 参见https://a.com的`pip install unsloth`命令。 was saved as 参见. The code span is now kept.
  3. 13ac9d4: code boundaries shared across validators. Re-parsing between the two validators could turn an indented line into code once a URL-only line was removed. Context\nhttps://nope.example\n [Document: hallucinated] kept the unsupported citation, while main removes it. The draft is now masked once for both validators, and the output matches main for these inputs.
  4. a780408: unused imports. research_runs.py no longer imports the two validators it stopped calling. That fixes the Source lint import-hoist blocker, and the storage tests now import the validators from core.research.citations.

Tests

  • All test_research*.py files at head: 511 passed.
  • New code tests: they fail with main's citations.py and pass at head. They also passed on markdown-it-py 3.0.0 at 5c7506e.
  • Codex items:
    • Round 1: all six P2 items reproduced on 3384073 and are fixed in 5c7506e.
    • Round 2: the HTML <pre>/<code> item behaves the same on main and is out of scope for this Markdown change.
    • Round 3: the boundary item is fixed in 13ac9d4.
    • Codex has not yet reviewed 13ac9d4/a780408.

CI

  • Fork CI on a780408: 20 of 22 workflows pass, including Lint CI, Core, Mac, Unsloth UI/API/GGUF/Update, all Windows suites, Wheel and Kaggle GPU.

  • Backend CI: Python 3.11 and 3.13 pass. Repo tests (CPU) has 1 failure out of 18,499: test_desktop_reliability_frontend_contract.py::test_tauri_collapse_removes_the_icon_rail_but_web_keeps_it, a frontend sidebar check this PR does not touch that also fails in main's own Backend CI at bf87e29. The studio extra mirror tests now pass.

  • Security audit: the three pip scan-packages legs fail, and they also fail on main bf87e29 with identical summaries, so the added markdown-it-py line adds no findings:

    Leg Summary
    extras 5 CRITICAL, 163 MEDIUM
    hf-stack 5 CRITICAL, 2 HIGH, 162 MEDIUM
    studio 5 CRITICAL, 254 MEDIUM

@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: a7804082bf

ℹ️ 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".

@danielhanchen

Copy link
Copy Markdown
Member

Confirmed this hits studio/backend/core/research/citations.py, where the link and citation pass still runs over fenced and inline code, so saved commands lose their URLs. Will get this reviewed.

@Etherll

Etherll commented Sep 12, 2026

Copy link
Copy Markdown
Collaborator

Deep Research citation validation now keeps command URLs, Python indexes, and citation-like literals in code. On a7804082b, the same report kept all seven examples. The merge base kept none. Both versions removed unsupported prose citations. The saved output rendered correctly in Windows Studio, and it remained unchanged after a reload.

All 442 affected tests passed, with parser checks on markdown-it-py 3.0 and 4.2. Both Windows installations completed successfully, and the upgrade and rollback preserved the saved chat. Five optional CI jobs remain red: the sidebar assertion also fails on the base, the DeepSeek timing failure did not reproduce locally, and package scans flag scikit-learn and unsloth-zoo. No required checks are reported. The image uses a deterministic report fixture without live-provider inference. Native browser coverage is Edge; non-Windows compatibility is reasoned. Before: 8ee07d6ae61e92544f63a733daf86f9b6a6b0ef2. After: a7804082bfcce603024b17a0e6101af90a1bafe5.

The same report before and after code-preserving citation validation

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.

3 participants