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

* dev/repro-agent: give Linear tickets a real fetch path + follow linked 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

* dev/repro: forward the Linear key through the --server env strip

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

* dev/repro: mirror LINEAR_API_KEY into the DATABRICKS_ name

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
This commit is contained in:
Serena Ruan
2026-08-04 19:34:48 +08:00
committed by GitHub
parent a858a6be9a
commit 45eab11d53
2 changed files with 66 additions and 5 deletions
+29 -4
View File
@@ -26,8 +26,31 @@ session and logs are things you *produce*, not inputs:
- `bug_url` (required) — a link to the bug report: a **GitHub issue URL** or a
**Linear ticket URL** (e.g. `https://github.com/omnigent-ai/omnigent/issues/1234`
or `https://linear.app/omnigent/issue/OMNI-1234`). Read the report to get the
bug description, steps, and version. Use `gh issue view` for GitHub or your
Linear tools for a Linear link.
bug description, steps, and version:
- **GitHub** → `gh issue view <url> --comments` (the CLI is on the machine).
- **Linear** → query the GraphQL API with `sys_os_shell`, using the Linear key
from your environment. It arrives as `LINEAR_API_KEY` locally or as
`DATABRICKS_LINEAR_API_KEY` under `--server` (the CLI→runner env strip only
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.:
```bash
KEY="${LINEAR_API_KEY:-$DATABRICKS_LINEAR_API_KEY}"
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 } } } }"}'
```
If neither `LINEAR_API_KEY` nor `DATABRICKS_LINEAR_API_KEY` is set (or the
fetch fails auth), you cannot read the ticket body — stop with verdict
`needs_more_info` naming the missing key rather than guessing the bug from the
URL slug.
- **Linear → linked GitHub issue.** A Linear ticket often links a GitHub issue
(in its `attachments`, description, or comments). If you find one, **always
fetch that GitHub issue too** (`gh issue view <url> --comments`) and treat it
as authoritative for the journey — the GitHub thread usually carries the
concrete repro steps, stack traces, and version that the Linear card only
summarizes. Reconcile the two: if they disagree, prefer the GitHub issue for
the technical detail and note the discrepancy.
- `public` (optional, boolean) — when `true`, share this session public-read as
the first thing you do in preflight (see Preflight). Off by default: locally
the session is already yours to browse; sharing is for watching a live run or
@@ -67,8 +90,10 @@ Your first turn is a fixed checklist — do all of it before Step 1:
2. **Confirm the workspace** (see above) and that you can reach the app and your
tooling with one `sys_os_shell` / tool check: the browser tools
(`browser_navigate` / `browser_snapshot` / `browser_click` / `browser_type`)
for UI journeys, and `sys_session_*` / HTTP for backend journeys. Confirm `gh`
is available if `bug_url` is a GitHub issue.
for UI journeys, and `sys_session_*` / HTTP for backend journeys. Confirm you
can read the report: `gh` is available for a GitHub issue, or a Linear key
(`LINEAR_API_KEY` or `DATABRICKS_LINEAR_API_KEY`) is set for a Linear ticket
(if it isn't, stop with `needs_more_info`).
If you cannot reach the app at all, stop and say so. Don't narrate a clean
preflight.
+37 -1
View File
@@ -31,6 +31,7 @@ from __future__ import annotations
import argparse
import json
import os
import re
import subprocess
import sys
@@ -47,6 +48,39 @@ def _die(msg: str) -> NoReturn:
raise SystemExit(1)
def _launch_env(bug_url: str) -> dict[str, str]:
"""Build the child env for ``omnigent run``, forwarding the Linear key.
Reading a Linear ticket needs ``DATABRICKS_LINEAR_API_KEY`` to reach the
agent's shell. Under ``--server`` the daemon→runner hop strips everything
not in its allowlist, and the ``DATABRICKS_`` prefix survives only the
CLI→daemon hop — so we also name the key in ``OMNIGENT_RUNNER_ENV_PASSTHROUGH``
(itself allowlisted), which tells the runner env-build to forward it the rest
of the way. Maintainers usually export the plain ``LINEAR_API_KEY`` locally,
so mirror that into the ``DATABRICKS_`` name when only the plain one is set.
Only kicks in for Linear URLs; if neither is set we warn rather than fail (the
agent stops with ``needs_more_info`` and names the missing key).
"""
env = os.environ.copy()
if "linear.app" not in bug_url:
return env
if not env.get("DATABRICKS_LINEAR_API_KEY") and env.get("LINEAR_API_KEY"):
env["DATABRICKS_LINEAR_API_KEY"] = env["LINEAR_API_KEY"]
if not env.get("DATABRICKS_LINEAR_API_KEY"):
print(
"warning: Linear URL but neither LINEAR_API_KEY nor "
"DATABRICKS_LINEAR_API_KEY is set — the agent won't be able to read "
"the ticket (it will stop with needs_more_info).",
file=sys.stderr,
)
return env
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)
return env
def _slug_from_bug_url(bug_url: str) -> str:
"""Derive a branch-safe slug from a bug URL or bare id.
@@ -174,10 +208,12 @@ def main() -> None:
cmd = ["omnigent", "run", _AGENT_REL, "-p", prompt]
if args.server is not None:
cmd += ["--server", args.server]
env = _launch_env(bug_url)
print(f"→ running: {' '.join(cmd)}")
print(f"→ cwd: {worktree}\n")
result = subprocess.run(cmd, cwd=str(worktree), check=False)
result = subprocess.run(cmd, cwd=str(worktree), env=env, check=False)
# Always keep the worktree; the authored test (if any) lives on its branch.
print(