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:
@@ -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
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user