feat: link PR attribution footer to the originating thread (#1539)

The "Made by Open SWE" PR footer linked to the generic dashboard
homepage. Point it at the dashboard thread that generated the PR
(/agents/<thread_id>), falling back to the homepage when no thread
id is available.

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
This commit is contained in:
Caroline di Vittorio 2026-06-16 09:50:18 -07:00 • committed by GitHub
parent 0108d764d9
commit 554d754585
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 61 additions and 11 deletions

View file

@ -6,8 +6,8 @@ from pathlib import Path
from .utils.authorship import ( from .utils.authorship import (
OPEN_SWE_BOT_EMAIL, OPEN_SWE_BOT_EMAIL,
OPEN_SWE_BOT_NAME, OPEN_SWE_BOT_NAME,
PR_ATTRIBUTION_FOOTER,
CollaboratorIdentity, CollaboratorIdentity,
build_pr_attribution_footer,
) )
from .utils.github_comments import UNTRUSTED_GITHUB_COMMENT_OPEN_TAG from .utils.github_comments import UNTRUSTED_GITHUB_COMMENT_OPEN_TAG
@ -375,7 +375,7 @@ This run was triggered by **{display_name}**. You author the work **as them**
{bot_coauthor_trailer} {bot_coauthor_trailer}
``` ```
- **PR body**: append this line to the bottom of the PR description (separated from the body by a blank line) when you open or update the draft PR. Do not duplicate it if it is already present. If the PR body already contains a legacy footer like `_Opened collaboratively by {display_name} and open-swe._`, replace that legacy footer with this line instead of appending a second footer: - **PR body**: append this line to the bottom of the PR description (separated from the body by a blank line) when you open or update the draft PR. Do not duplicate it if it is already present. If the PR body already contains a `Made by [Open SWE]` footer pointing at a different link, or a legacy footer like `_Opened collaboratively by {display_name} and open-swe._`, replace that existing footer with this line instead of appending a second footer:
``` ```
{pr_attribution_footer} {pr_attribution_footer}
@ -384,12 +384,15 @@ This run was triggered by **{display_name}**. You author the work **as them**
If you forget the trailer on a local commit that has not been pushed, fix it with `git commit --amend` before pushing — do not push without it. If the commit has already been pushed, leave it as-is and add the trailer to your next commit; never rewrite remote history to fix it.""" If you forget the trailer on a local commit that has not been pushed, fix it with `git commit --amend` before pushing — do not push without it. If the commit has already been pushed, leave it as-is and add the trailer to your next commit; never rewrite remote history to fix it."""
def _render_collaboration_section(identity: CollaboratorIdentity | None) -> str: def _render_collaboration_section(
identity: CollaboratorIdentity | None,
thread_url: str | None = None,
) -> str:
if identity is None: if identity is None:
return "" return ""
return COLLABORATION_TEMPLATE.format( return COLLABORATION_TEMPLATE.format(
display_name=identity.display_name, display_name=identity.display_name,
pr_attribution_footer=PR_ATTRIBUTION_FOOTER, pr_attribution_footer=build_pr_attribution_footer(thread_url),
bot_coauthor_trailer=f"Co-authored-by: {OPEN_SWE_BOT_NAME} <{OPEN_SWE_BOT_EMAIL}>", bot_coauthor_trailer=f"Co-authored-by: {OPEN_SWE_BOT_NAME} <{OPEN_SWE_BOT_EMAIL}>",
) )
@ -446,6 +449,7 @@ def construct_system_prompt(
create_prs: bool = False, create_prs: bool = False,
default_repo: dict[str, str] | None = None, default_repo: dict[str, str] | None = None,
repo_custom_instructions: str | None = None, repo_custom_instructions: str | None = None,
thread_url: str | None = None,
) -> str: ) -> str:
default_prompt_section = _load_default_prompt() default_prompt_section = _load_default_prompt()
if default_repo and default_repo.get("owner") and default_repo.get("name"): if default_repo and default_repo.get("owner") and default_repo.get("name"):
@ -468,7 +472,7 @@ def construct_system_prompt(
linear_issue_number=linear_issue_number or "<ISSUE_NUMBER>", linear_issue_number=linear_issue_number or "<ISSUE_NUMBER>",
default_prompt_section=default_prompt_section, default_prompt_section=default_prompt_section,
pr_policy_override_section=ALWAYS_CREATE_PR_SECTION if create_prs else "", pr_policy_override_section=ALWAYS_CREATE_PR_SECTION if create_prs else "",
collaboration_section=_render_collaboration_section(triggering_user_identity), collaboration_section=_render_collaboration_section(triggering_user_identity, thread_url),
repo_instructions_section=_render_repo_instructions_section(repo_custom_instructions), repo_instructions_section=_render_repo_instructions_section(repo_custom_instructions),
commit_identity_name=commit_identity_name, commit_identity_name=commit_identity_name,
commit_identity_email=commit_identity_email, commit_identity_email=commit_identity_email,

View file

@ -81,6 +81,7 @@ from .utils.authorship import (
OPEN_SWE_BOT_NAME, OPEN_SWE_BOT_NAME,
resolve_triggering_user_identity, resolve_triggering_user_identity,
) )
from .utils.dashboard_links import dashboard_thread_url
from .utils.github_app import ( from .utils.github_app import (
get_github_app_installation_token_with_expiry, get_github_app_installation_token_with_expiry,
) )
@ -691,6 +692,7 @@ async def get_agent(config: RunnableConfig) -> Pregel:
create_prs=always_create_prs, create_prs=always_create_prs,
default_repo=prompt_default_repo, default_repo=prompt_default_repo,
repo_custom_instructions=repo_custom_instructions, repo_custom_instructions=repo_custom_instructions,
thread_url=dashboard_thread_url(thread_id),
), ),
tools=[ tools=[
http_request, http_request,

View file

@ -16,7 +16,15 @@ OPEN_SWE_BOT_NAME = "open-swe[bot]"
# accepts, which broke preview deploys on commits carrying this co-author. # accepts, which broke preview deploys on commits carrying this co-author.
OPEN_SWE_BOT_EMAIL = "open-swe@users.noreply.github.com" OPEN_SWE_BOT_EMAIL = "open-swe@users.noreply.github.com"
PR_ATTRIBUTION_FOOTER = "Made by [Open SWE](https://openswe.vercel.app)" PR_ATTRIBUTION_TEXT = "Made by [Open SWE]"
PR_ATTRIBUTION_DEFAULT_URL = "https://openswe.vercel.app"
PR_ATTRIBUTION_FOOTER = f"{PR_ATTRIBUTION_TEXT}({PR_ATTRIBUTION_DEFAULT_URL})"
def build_pr_attribution_footer(thread_url: str | None = None) -> str:
"""Build the Open SWE PR footer, linking the run's thread when available."""
url = thread_url.strip() if isinstance(thread_url, str) and thread_url.strip() else ""
return f"{PR_ATTRIBUTION_TEXT}({url or PR_ATTRIBUTION_DEFAULT_URL})"
@dataclass(frozen=True) @dataclass(frozen=True)
@ -159,17 +167,21 @@ def add_bot_coauthor_trailer(commit_message: str) -> str:
def add_pr_collaboration_note( def add_pr_collaboration_note(
pr_body: str, pr_body: str,
identity: CollaboratorIdentity | None = None, identity: CollaboratorIdentity | None = None,
thread_url: str | None = None,
) -> str: ) -> str:
"""Append the Open SWE attribution footer to a PR body. """Append the Open SWE attribution footer to a PR body.
The PR is opened as the triggering user, so the body only credits Open SWE The PR is opened as the triggering user, so the body only credits Open SWE
as the collaborator. Any legacy double-attribution footer is replaced. as the collaborator. The footer links the run's thread when available. Any
legacy double-attribution footer is replaced.
""" """
normalized_body = pr_body.rstrip() normalized_body = pr_body.rstrip()
note = PR_ATTRIBUTION_FOOTER note = build_pr_attribution_footer(thread_url)
if note in normalized_body: if note in normalized_body:
return normalized_body return normalized_body
if PR_ATTRIBUTION_TEXT in normalized_body:
return normalized_body
legacy_footers: list[str] = [] legacy_footers: list[str] = []
if identity is not None: if identity is not None:

View file

@ -126,12 +126,27 @@ def test_construct_system_prompt_includes_github_login_in_pr_footer() -> None:
assert "git config user.email 1234+octocat@users.noreply.github.com" in prompt assert "git config user.email 1234+octocat@users.noreply.github.com" in prompt
assert _BOT_TRAILER in prompt assert _BOT_TRAILER in prompt
assert "Made by [Open SWE](https://openswe.vercel.app)" in prompt assert "Made by [Open SWE](https://openswe.vercel.app)" in prompt
assert ( assert "replace that existing footer with this line" in prompt
"replace that legacy footer with this line instead of appending a second footer" in prompt
)
assert "`_Opened collaboratively by Mona Lisa and open-swe._`" in prompt assert "`_Opened collaboratively by Mona Lisa and open-swe._`" in prompt
def test_construct_system_prompt_footer_links_thread_when_provided() -> None:
identity = CollaboratorIdentity(
display_name="octocat",
commit_name="octocat",
commit_email="1234+octocat@users.noreply.github.com",
)
prompt = construct_system_prompt(
working_dir="/workspace",
triggering_user_identity=identity,
thread_url="https://openswe.vercel.app/agents/abc-123",
)
assert "Made by [Open SWE](https://openswe.vercel.app/agents/abc-123)" in prompt
assert "Made by [Open SWE](https://openswe.vercel.app)" not in prompt
def test_construct_system_prompt_shell_escapes_user_name() -> None: def test_construct_system_prompt_shell_escapes_user_name() -> None:
import shlex import shlex
@ -168,6 +183,23 @@ def test_add_pr_collaboration_note_replaces_legacy_footer() -> None:
) )
def test_add_pr_collaboration_note_links_thread() -> None:
body = "## Description\nDone."
assert add_pr_collaboration_note(
body, thread_url="https://openswe.vercel.app/agents/abc-123"
) == ("## Description\nDone.\n\nMade by [Open SWE](https://openswe.vercel.app/agents/abc-123)")
def test_add_pr_collaboration_note_skips_when_footer_present_with_other_link() -> None:
body = "## Description\nDone.\n\nMade by [Open SWE](https://openswe.vercel.app)"
assert (
add_pr_collaboration_note(body, thread_url="https://openswe.vercel.app/agents/abc-123")
== body
)
def test_resolve_triggering_user_identity_combines_slack_name_with_github_login() -> None: def test_resolve_triggering_user_identity_combines_slack_name_with_github_login() -> None:
identity = resolve_triggering_user_identity( identity = resolve_triggering_user_identity(
{ {