dev/repro-agent: give Linear tickets a real fetch path + follow linked GitHub issues - #4047
Conversation
…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
There was a problem hiding this comment.
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
curlfetch path (including required auth header format) and an “honest fallback” (needs_more_info) whenLINEAR_API_KEYis 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 (
ghfor GitHub issues,LINEAR_API_KEYfor Linear tickets).
| **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 } } } }"}' |
|
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
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
There was a problem hiding this comment.
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-fto fail on non-2xx) makes failures more visible/actionable insys_os_shelloutput.
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_PASSTHROUGHparsing here doesn't strip whitespace from each name. Since the runner env builder explicitly tolerates whitespace around commas, an existing value likeFOO, BARwill produce entries with leading spaces, causing the membership check to missDATABRICKS_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
There was a problem hiding this comment.
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"). Sinceidis 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_envonly activates whenbug_urlcontainslinear.app. The script also accepts bare Linear identifiers likeOMNI-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_PASSTHROUGHentries 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()]
There was a problem hiding this comment.
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",
00332de to
8cb4260
Compare
|
🏷️ Doc impact: 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 |
There was a problem hiding this comment.
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 whenbug_urlcontainslinear.app. However, this script also accepts bare Linear identifiers likeOMNI-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)
Summary
The local
dev/repro-agentreproduced GitHub-issue bugs well but did poorly onLinear tickets. The cause wasn't that GitHub gives more context — it's that
GitHub had a working retrieval path (
gh issue view) while Linear hadnone:
AGENTS.mdtold the agent to use "your Linear tools", but this localspec 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-syncagentalready uses, and makes the API key actually reach the agent's shell under
--server.1. Real Linear fetch path (
AGENTS.md)https://api.linear.app/graphql, headerAuthorization: <key>(noBearer), viasys_os_shellcurl. Fetches title / description / comments /attachments.
needs_more_infonaming the missing key instead of guessing from the slug.(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
--serverenv strip (dev/repro.py)Under
--server, the CLI → daemon → runner hops strip every env var notallowlisted, so a bare
LINEAR_API_KEYnever reaches the agent. Three layers:DATABRICKS_LINEAR_API_KEYLINEAR_API_KEYDATABRICKS_prefixLC_/MLFLOW_/OTEL_/OMNIGENT_OTEL_+OMNIGENT_RUNNER_ENV_PASSTHROUGHnamessandbox: none)The
DATABRICKS_prefix survives only the first hop. So when a Linear URL ispassed and
DATABRICKS_LINEAR_API_KEYis set,dev/repro.pynow names it inOMNIGENT_RUNNER_ENV_PASSTHROUGH(itself allowlisted) — which tells thedaemon→runner env-build to forward it the rest of the way.
AGENTS.mdreadswhichever name is present (
LINEAR_API_KEYlocally,DATABRICKS_LINEAR_API_KEYunder
--server). If a Linear URL is passed without the key,dev/repro.pywarns 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_BEARERfor the same reason.3. Companion change (omnigent-internal — not in this repo)
The
repro-agentCI workflow must setDATABRICKS_LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }}in the run step, next to the existingDATABRICKS_BEARER. Without it,--serverLinear runs still can't read theticket. 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 --checkondev/repro.pypass;pre-commitpassed. The three envhops were traced against
cli._build_host_daemon_env,connect._build_runner_env(allowlist +OMNIGENT_RUNNER_ENV_PASSTHROUGHforwarding), and
os_env.build_helper_env(sandbox: noneinherits verbatim).The Linear GraphQL endpoint + auth-header form match the internal
issue-syncagent, which uses the identical pattern against live Linear.
N/Afor automatedtests — this is agent operating guidance + a maintainer launcher script.
Demo
N/A — no UI / frontend change.
Type of change
Test coverage
Coverage notes
Manual verification: agent operating guidance (
AGENTS.md) + a maintainer-onlylauncher (
dev/repro.py, not packaged). Env-forwarding was verified by tracingthe three allowlist filters in the codebase; the Linear GraphQL call matches the
internal
issue-syncagent's live-tested pattern. No automated test coversprompt text or the dev launcher.
This pull request and its description were written by Isaac.