Skip to content

dev/repro-agent: give Linear tickets a real fetch path + follow linked GitHub issues - #4047

Merged
serena-ruan merged 3 commits into
mainfrom
repro-agent-linear
Aug 4, 2026
Merged

dev/repro-agent: give Linear tickets a real fetch path + follow linked GitHub issues#4047
serena-ruan merged 3 commits into
mainfrom
repro-agent-linear

Conversation

@serena-ruan

@serena-ruan serena-ruan commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

The local dev/repro-agent reproduced GitHub-issue bugs well but did poorly on
Linear tickets. The cause wasn't that GitHub gives more context — it's that
GitHub had a working retrieval path (gh issue view) while Linear had
none: AGENTS.md told the agent to use "your Linear tools", but this local
spec wires up no Linear MCP server. So Linear runs couldn't read the ticket body
and fell back to guessing the bug from the URL slug, leaving Step 1 (reconstruct
the journey) — and everything downstream — built on air.

This wires Linear to the same GraphQL path the internal issue-sync agent
already uses, and makes the API key actually reach the agent's shell under
--server.

1. Real Linear fetch path (AGENTS.md)

  • Endpoint https://api.linear.app/graphql, header Authorization: <key> (no
    Bearer), via sys_os_shell curl. Fetches title / description / comments /
    attachments.
  • Honest fallback: if no key is set or auth fails, stop with
    needs_more_info naming the missing key instead of guessing from the slug.
  • Follow linked GitHub issues: a Linear ticket often links a GitHub issue
    (attachments/description/comments). When it does, the agent now always
    fetches that GitHub issue too and treats it as authoritative for the technical
    journey (repro steps, stack traces, version) — this is a big part of why
    GitHub-first runs reproduced better.

2. Get the key past the --server env strip (dev/repro.py)

Under --server, the CLI → daemon → runner hops strip every env var not
allowlisted, so a bare LINEAR_API_KEY never reaches the agent. Three layers:

Hop keeps DATABRICKS_LINEAR_API_KEY bare LINEAR_API_KEY
CLI→daemon allowlist + DATABRICKS_ prefix ✅ (prefix)
daemon→runner allowlist + LC_/MLFLOW_/OTEL_/OMNIGENT_OTEL_ + OMNIGENT_RUNNER_ENV_PASSTHROUGH names ❌ prefix alone dies here
runner→agent (sandbox: none) inherits runner env verbatim ✅ (whatever hop 2 delivered)

The DATABRICKS_ prefix survives only the first hop. So when a Linear URL is
passed and DATABRICKS_LINEAR_API_KEY is set, dev/repro.py now names it in
OMNIGENT_RUNNER_ENV_PASSTHROUGH (itself allowlisted) — which tells the
daemon→runner env-build to forward it the rest of the way. AGENTS.md reads
whichever name is present (LINEAR_API_KEY locally, DATABRICKS_LINEAR_API_KEY
under --server). If a Linear URL is passed without the key, dev/repro.py
warns rather than failing (the agent then stops with needs_more_info).

This mirrors exactly how the internal repro workflow already routes the LLM key
through DATABRICKS_BEARER for the same reason.

3. Companion change (omnigent-internal — not in this repo)

The repro-agent CI workflow must set DATABRICKS_LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }} in the run step, next to the existing
DATABRICKS_BEARER. Without it, --server Linear runs still can't read the
ticket. Local dev is unaffected — a maintainer just export LINEAR_API_KEY=...
and the local daemon path inherits it.

Test Plan

Docs/prompt + launcher change (not shipped in the wheel). ruff check +
ruff format --check on dev/repro.py pass; pre-commit passed. The three env
hops were traced against cli._build_host_daemon_env,
connect._build_runner_env (allowlist + OMNIGENT_RUNNER_ENV_PASSTHROUGH
forwarding), and os_env.build_helper_env (sandbox: none inherits verbatim).
The Linear GraphQL endpoint + auth-header form match the internal issue-sync
agent, which uses the identical pattern against live Linear. N/A for automated
tests — this is agent operating guidance + a maintainer launcher script.

Demo

N/A — no UI / frontend change.

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update

Test coverage

  • New tests added
  • Manual verification completed
  • Not applicable

Coverage notes

Manual verification: agent operating guidance (AGENTS.md) + a maintainer-only
launcher (dev/repro.py, not packaged). Env-forwarding was verified by tracing
the three allowlist filters in the codebase; the Linear GraphQL call matches the
internal issue-sync agent's live-tested pattern. No automated test covers
prompt text or the dev launcher.

This pull request and its description were written by Isaac.

…d GitHub issues

The local repro-agent pointed Linear tickets at nonexistent "Linear tools",
so Linear runs had no way to read the ticket body and fell back to guessing
from the URL slug — noticeably worse reproductions than GitHub issues, which
have a working `gh issue view` path.

Wire Linear to the same GraphQL path the internal issue-sync agent uses
(api.linear.app/graphql, `Authorization: $LINEAR_API_KEY`, no Bearer), pulling
description/comments/attachments via sys_os_shell. When the key is absent or
auth fails, stop with needs_more_info naming the missing key instead of
guessing. Also: when a Linear ticket links a GitHub issue, always fetch that
issue too and treat it as authoritative for the technical journey — that
richer thread is why GitHub-first runs reproduced better.

Co-authored-by: Isaac
Copilot AI lite review requested due to automatic review settings August 4, 2026 10:11
@github-actions github-actions Bot added the size/S Pull request size: S label Aug 4, 2026

Copilot AI 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.

Pull request overview

Updates the dev/repro-agent operating instructions so Linear tickets have an explicit, working retrieval path (via Linear’s GraphQL API using LINEAR_API_KEY), and so the agent follows any linked GitHub issues as the authoritative technical source when present.

Changes:

  • Document a concrete Linear GraphQL curl fetch path (including required auth header format) and an “honest fallback” (needs_more_info) when LINEAR_API_KEY is missing or auth fails.
  • Add guidance to always fetch and prefer linked GitHub issues referenced from Linear tickets.
  • Update preflight instructions to validate report-readability (gh for GitHub issues, LINEAR_API_KEY for Linear tickets).

Comment thread dev/repro-agent/AGENTS.md Outdated
Comment on lines +34 to +38
**no** `Bearer` prefix). Fetch the ticket by its identifier, e.g.:
```bash
curl -s https://api.linear.app/graphql \
-H "Authorization: $LINEAR_API_KEY" -H 'Content-Type: application/json' \
-d '{"query":"{ issue(id: \"OMNI-1234\") { identifier title description url state { name } comments(first: 50) { nodes { body } } attachments(first: 20) { nodes { url } } } }"}'
@omnigent-ci

omnigent-ci Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Polly AI Review

Review: dev/repro-agent Linear fetch path

This is a prose-only change to dev/repro-agent/AGENTS.md (agent operating guidance, not shipped code). No code paths, tests, or dependencies change, so most of the usual correctness/lockfile surface doesn't apply. Assessment focuses on the operational instructions themselves.

1. Blocking issues

None. The change replaces a dead instruction ("use your Linear tools" — no such tools are wired) with a concrete, working retrieval path and an honest needs_more_info fallback. That is a strict improvement and introduces no correctness regression in shipping code.

2. Security notes

  • Secret handling is done right. The example passes Authorization: $LINEAR_API_KEY as a literal, unexpanded string in the sys_os_shell command. The shell expands it at runtime, so the recorded command in the session transcript shows $LINEAR_API_KEY, not the key value — important because these sessions are browsable and can be public: true shared. Worth preserving this pattern explicitly; if a future author "helpfully" inlines the key or echoes the resolved command, it would leak into a public-read session. A one-line caution ("never echo the expanded key / the curl command") would harden this.
  • Untrusted-input interpolation (non-blocking, see below). The ticket identifier is derived from bug_url and interpolated into a GraphQL string inside the shell command. bug_url is operator-supplied so exploit risk is low, but the doc itself flags the linked report as untrusted, so the identifier deserves the same discipline.

3. Non-blocking notes

  • Validate the identifier before interpolating. issue(id: \"OMNI-1234\") is built by string interpolation into a single-quoted JSON payload passed to curl. Recommend instructing the agent to extract the identifier from the URL and validate it against a strict pattern (e.g. ^[A-Z][A-Z0-9]*-\d+$) before substituting, so a malformed/crafted slug can't break out of the GraphQL string or the shell quoting.
  • Auth-failure detection. curl -s hides the HTTP status and Linear returns a 200 with an errors array on some failures rather than a hard non-zero exit. The "fetch fails auth → needs_more_info" rule relies on the agent noticing this; consider suggesting -w '%{http_code}' or checking the response body for an errors/data: null shape so the fallback actually triggers instead of the agent proceeding on an empty data.
  • issue(id:) argument semantics. Confirm Linear's issue(id:) query accepts the human identifier (OMNI-1234) and not only a UUID. The PR states this mirrors the working internal issue-sync agent, which is reasonable evidence, but if that agent actually resolves a UUID first, the copied snippet could silently return null. Low confidence — worth a quick confirmation, not a blocker.

4. Summary

A well-motivated, self-contained documentation fix that turns a broken Linear retrieval instruction into a real GraphQL fetch path with a truthful fallback, and adds a sensible "follow the linked GitHub issue" rule. Secret handling via the unexpanded $LINEAR_API_KEY literal is correct. No blocking issues; the only follow-ups worth making are hardening the identifier interpolation and making the auth-failure detection robust so the needs_more_info fallback reliably fires.


Automated review by Polly · workflow run

Reading a Linear ticket needs the key in the agent's shell, but under --server
the CLI->daemon->runner hops strip everything not allowlisted. The DATABRICKS_
prefix survives only the first hop; the daemon->runner hop has no DATABRICKS_
prefix. So dev/repro.py now names DATABRICKS_LINEAR_API_KEY in
OMNIGENT_RUNNER_ENV_PASSTHROUGH (itself allowlisted) when a Linear URL is passed
and the key is set, which forwards it the rest of the way. AGENTS.md reads
whichever name is present (LINEAR_API_KEY locally, DATABRICKS_LINEAR_API_KEY
under --server). Warns rather than fails when the key is missing.

Companion change (omnigent-internal): the repro-agent CI workflow must set
DATABRICKS_LINEAR_API_KEY from secrets.LINEAR_API_KEY in the run step, mirroring
how it already sets DATABRICKS_BEARER for the LLM key.

Co-authored-by: Isaac
Copilot AI review requested due to automatic review settings August 4, 2026 10:44
@github-actions github-actions Bot added size/M Pull request size: M and removed size/S Pull request size: S labels Aug 4, 2026
Maintainers typically export the plain LINEAR_API_KEY locally, so copy it into
DATABRICKS_LINEAR_API_KEY when only the plain name is set — then the same
passthrough forwarding carries it past the --server env strip. Warn only when
neither is set.

Co-authored-by: Isaac

Copilot AI 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.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Suppressed comments (2)

dev/repro-agent/AGENTS.md:41

  • The example uses curl -s, which can hide network/TLS failures and makes it easier to miss auth/HTTP problems, even though the next paragraph tells the agent to stop if auth fails. Using -sS (and -f to fail on non-2xx) makes failures more visible/actionable in sys_os_shell output.
    curl -s https://api.linear.app/graphql \
      -H "Authorization: $KEY" -H 'Content-Type: application/json' \
      -d '{"query":"{ issue(id: \"OMNI-1234\") { identifier title description url state { name } comments(first: 50) { nodes { body } } attachments(first: 20) { nodes { url } } } }"}'

dev/repro.py:76

  • OMNIGENT_RUNNER_ENV_PASSTHROUGH parsing here doesn't strip whitespace from each name. Since the runner env builder explicitly tolerates whitespace around commas, an existing value like FOO, BAR will produce entries with leading spaces, causing the membership check to miss DATABRICKS_LINEAR_API_KEY (and potentially append duplicates). Strip each entry so forwarding is reliable.
            "the ticket (it will stop with needs_more_info).",
            file=sys.stderr,
        )
        return env

Copilot AI review requested due to automatic review settings August 4, 2026 10:47

Copilot AI 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.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Suppressed comments (3)

dev/repro-agent/AGENTS.md:36

  • The prose says to “fetch the ticket by its identifier”, but the example query uses issue(id: "OMNI-1234"). Since id is commonly interpreted as an internal opaque ID in GraphQL APIs, this mismatch is confusing and can lead to incorrect copy/paste usage.
    forwards the `DATABRICKS_`-prefixed name), so read whichever is set. Endpoint
    `https://api.linear.app/graphql`, header `Authorization: <key>` — **no**
    `Bearer` prefix. Fetch the ticket by its identifier, e.g.:

dev/repro.py:66

  • _launch_env only activates when bug_url contains linear.app. The script also accepts bare Linear identifiers like OMNI-1234, but in that case the Linear API key won’t be forwarded under --server, so the agent still can’t read the ticket.
    if "linear.app" not in bug_url:
        return env

dev/repro.py:77

  • OMNIGENT_RUNNER_ENV_PASSTHROUGH entries aren’t normalized: items with leading/trailing spaces will be preserved, which can cause duplicate entries and makes the env var harder to read/debug.
    names = [n for n in env.get("OMNIGENT_RUNNER_ENV_PASSTHROUGH", "").split(",") if n.strip()]

Copilot AI review requested due to automatic review settings August 4, 2026 11:16

Copilot AI 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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

Suppressed comments (2)

dev/repro.py:80

  • When parsing OMNIGENT_RUNNER_ENV_PASSTHROUGH, the code filters out empty entries via n.strip() but keeps the unstripped strings. If the existing value contains whitespace (e.g. "FOO, DATABRICKS_LINEAR_API_KEY"), the membership test can miss the key and append a duplicate, producing a messy passthrough list.
    names = [n for n in env.get("OMNIGENT_RUNNER_ENV_PASSTHROUGH", "").split(",") if n.strip()]
    if "DATABRICKS_LINEAR_API_KEY" not in names:
        names.append("DATABRICKS_LINEAR_API_KEY")
    env["OMNIGENT_RUNNER_ENV_PASSTHROUGH"] = ",".join(names)

omnigent/host/connect.py:467

  • This change affects which env vars survive the CLI→daemon allowlist in remote (--server) mode. There are existing unit tests for _build_host_daemon_env in tests/cli/test_backend.py, but none assert that OMNIGENT_RUNNER_ENV_PASSTHROUGH is preserved for remote daemons; adding a regression test would help ensure passthrough continues working.
        # The operator's env-forwarding control var itself. Without it here, the
        # var is stripped before it reaches the daemon in --server mode (the
        # remote daemon prefixes are DATABRICKS_ + LC_/MLFLOW_/OTEL_/OMNIGENT_OTEL_,
        # not plain OMNIGENT_), so _build_runner_env never sees the names it lists
        # and the whole passthrough is a no-op remotely. It carries only env var
        # NAMES, not secrets, so allowlisting it leaks nothing on its own.
        # (Literal, not RUNNER_ENV_PASSTHROUGH_ENV_VAR, which is defined below.)
        "OMNIGENT_RUNNER_ENV_PASSTHROUGH",

Copilot AI review requested due to automatic review settings August 4, 2026 11:33
@serena-ruan
serena-ruan merged commit 45eab11 into main Aug 4, 2026
103 of 108 checks passed
@serena-ruan
serena-ruan deleted the repro-agent-linear branch August 4, 2026 11:34
@github-actions github-actions Bot added the no-doc-update Merged PR does not need a docs update label Aug 4, 2026
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

🏷️ Doc impact: no-doc-update

Changes are confined to internal dev tooling (dev/repro.py and dev/repro-agent/AGENTS.md) for reading Linear tickets during bug repro — not a user-facing surface, integration, or built-in policy.

Auto-classified on merge. Set the label manually before merging to override. · run

Copilot AI 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.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Suppressed comments (2)

dev/repro.py:66

  • _launch_env() only forwards the Linear API key when bug_url contains linear.app. However, this script also accepts bare Linear identifiers like OMNI-1234 (see _slug_from_bug_url() and usage examples). When running with --server, a bare identifier would skip env forwarding and the agent won't be able to fetch the Linear ticket.
    if "linear.app" not in bug_url:
        return env

dev/repro.py:80

  • When extending OMNIGENT_RUNNER_ENV_PASSTHROUGH, the current parsing preserves any surrounding whitespace (e.g. values like "NAME1, NAME2"), which can cause the membership check to miss an already-present entry and append duplicates. Stripping names on read keeps the env var clean and makes the check reliable.
    names = [n for n in env.get("OMNIGENT_RUNNER_ENV_PASSTHROUGH", "").split(",") if n.strip()]
    if "DATABRICKS_LINEAR_API_KEY" not in names:
        names.append("DATABRICKS_LINEAR_API_KEY")
    env["OMNIGENT_RUNNER_ENV_PASSTHROUGH"] = ",".join(names)

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

Labels

no-doc-update Merged PR does not need a docs update size/M Pull request size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants