chore: add bug-triage skill for agent-driven issue triage (#1725)

* chore: add bug-triage skill for agent-driven issue triage

Add .skills/bug-triage/ with SKILL.md and push_fix_to_github.py script.

The skill provides a complete triage workflow:
- Gather bug context from chat/issues/live observation
- Search for duplicate GitHub issues
- File well-structured issues with root cause analysis
- Push fix PRs via GitHub API (no local checkout needed)
- Git archaeology (git log -S) for regression tracking
- NPM package regression diffing
- Remote code inspection without local clone

Reference the skill in AGENTS.md so any agent working on this repo
can discover and follow the triage workflow automatically.

Tested across 100+ real bug triages on ComposioHQ/agent-orchestrator.

* chore: add clickable issue/PR links as real-world examples

Add concrete examples with links to actual issues and PRs:
- #1129: deep diagnosis vs surface-level triage
- #1151: placeholder URL RCA
- #1391: CSS regression via git log -S archaeology
- PR #1523: optional TypeScript interface fields
- PR #1608: npm package regression diffing

* chore: add formatting rule — always linkify issue/PR references

Any agent following this skill must include clickable URLs when
mentioning issues or PRs. Bare '#123' without links is not allowed.

* fix: address review feedback — use existing skills/ dir, fix bugs

- Move .skills/ → skills/ (repo already has a skills/ directory)
- Fix label name: 'priority:medium' → 'priority: medium' (with space)
- Fix issue-assets branch naming: use slug instead of issue number
  (issue doesn't exist yet at upload time)
- Fix base64 command: portable across Linux and macOS (tr -d '\n')
- push_fix_to_github.py: allow empty NEW_STRING for deletion edits
- push_fix_to_github.py: warn on multiple OLD_STRING matches
- push_fix_to_github.py: create branch before fetching file (SHA race)
- push_fix_to_github.py: configurable BASE_BRANCH (not hardcoded main)
- Update all path references in AGENTS.md and SKILL.md

* fix: correct stale .skills/ path in AGENTS.md How to load section

* feat: add cross-platform triage awareness (Windows/macOS/Linux)

Add Step 1b covering:
- When to ask for OS/shell/runtime/reproducibility
- Common Windows-specific bug patterns (paths, shell syntax, ConPTY,
  named pipes, NTFS case-insensitivity, localhost IPv6 stalls)
- Key cross-platform files (platform.ts, CROSS_PLATFORM.md, etc.)
- Tagging OS-specific issues with 'to-reproduce'

Based on the actual Windows support implementation in #1025
(platform.ts, runtime-process, CROSS_PLATFORM.md).

* feat: add 6 triage improvements from real failure patterns

1. Environment Info Collection (Step 1):
   - Standard template: OS, shell, runtime, AO version, Node version, install method
   - Prevents wasted time tracing wrong code versions

2. Duplicate Search Strategy (Step 2):
   - Search by symptom, component, AND error message
   - Always search --state all (open + closed — bugs regress)
   - Check PRs too (fixes sometimes land without issues)

3. Stop-and-Ask Triggers (Step 1c):
   - Explicit criteria: 3 failed hypotheses, can't reproduce, upstream bug,
     UI-only bug without screenshot, unknown environment
   - Includes template for asking the reporter

4. Pre-Submission Checklist (Step 4.1b):
   - Reporter attribution, commit hash, AO version, confidence score,
     cross-links, concrete reproduction steps, screenshots ready
   - Verify all before creating the issue

5. Confidence Scoring (Step 4.4):
   - High/Medium/Low with clear criteria
   - Maps to labels: bug only / to-explore / to-reproduce
   - Example from PR #1608 where high confidence was wrong

6. Cross-Linking Related Issues (Step 4.5):
   - Search by subsystem after filing
   - Include Related section with one-line descriptions
   - Helps maintainers see patterns across issues

7. Subsystem-Specific Triage Quick Reference:
   - Table mapping subsystems to required info and key files
   - Common misrouting patterns (terminal, stuck session, config)

* feat: add report gate, local diagnostics with ao events, deduplicate

New sections:
- Step 0a: Platform-specific context gathering (Discord/Slack/GitHub/live)
- Step 0b: Minimum Viable Report Gate — required fields (what/where/when)
  plus 2-of-4 supporting (OS, version, reproducibility, steps)
- Step 0c: Local Diagnostics — auto-gather environment, process health,
  AO event log (ao events list/search/stats), session state files,
  reproducibility testing. Covers ao events commands with all flags.

Deduplication:
- Step 1b: removed repeated OS/shell/runtime questions (now in Step 1.2)
- Step 1c: removed 'environment unknown' and 'can't reproduce' triggers
  (covered by report gate in Step 0b)

* refactor: compress bug-triage skill from 575→311 lines

Merge overlapping sections (Steps 0/0b/0c/1 → single Gather+Investigate flow),
move reference material to Appendix, deduplicate pitfalls, compress code blocks.
All factual content preserved: commands, file paths, labels, examples, links.

* docs: add skills/ README with agent-specific install instructions

Covers Claude Code, Codex CLI, Cursor, Windsurf, Copilot, Gemini CLI,
and Agent Orchestrator. Includes available skills table and how to write
new skills.

* docs: add Skills section to CLAUDE.md referencing skills/ directory

Links all 4 skills with when-to-load guidance, plus pointer to
skills/README.md for installing into other agents.

* fix: resolve PR review comments — remove Hermes-specific refs, portable base64

- Replace execute_code references with agent-agnostic 'Python script' wording
- Replace base64 -d (Linux-only) with python3 -c (portable across macOS/Linux/Windows)

---------

Co-authored-by: AO Bot <ao-bot@composio.dev>
This commit is contained in:
i-trytoohard 2026-05-12 20:51:00 +05:30 committed by GitHub
parent 334611b375
commit 0133bcdc88
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 556 additions and 0 deletions

View File

@ -29,6 +29,26 @@ Monorepo (pnpm) with packages: `core`, `cli`, `web`, and `plugins/*`. The web da
Full guidelines with AO-specific context: see "Working Principles" in CLAUDE.md.
## Skills
Agents working on this repo should use these checked-in skills:
### Bug Triage (`skills/bug-triage/`)
**When to use:** Any time a bug is reported — in chat, issues, or live observation.
**What it covers:**
- Full triage workflow: gather context → search duplicates → file/update GitHub issues → push fix PRs
- Root cause analysis with `git log -S` archaeology and upstream dependency research
- GitHub API-based file editing (no local checkout needed) via `scripts/push_fix_to_github.py`
- NPM package regression diffing
- Remote code inspection when the repo isn't cloned locally
**How to load:** Read `skills/bug-triage/SKILL.md` and follow its step-by-step workflow. The `scripts/` directory contains executable tools:
- `push_fix_to_github.py` — Push a single-file fix and create a PR entirely via GitHub API
**Always pull latest main before triaging.** Stale code = bad triage. No exceptions.
## Key Files
- `packages/core/src/types.ts` — All plugin interfaces (Agent, Runtime, Workspace, etc.)

View File

@ -363,6 +363,19 @@ All importable from `@aoagents/ao-core` unless noted:
| `packages/cli/src/lib/running-state.ts` | RunningState + LastStopState management (register/unregister, last-stop read/write) |
| `packages/web/src/components/ProjectSidebar.tsx` | Sidebar — always shows all projects' sessions |
## Skills
The `skills/` directory contains reusable workflow documents for common tasks. Load them before starting work:
| Skill | When to load |
|-------|-------------|
| [`skills/bug-triage/SKILL.md`](skills/bug-triage/SKILL.md) | Triage a bug report — investigate, search duplicates, file GitHub issues, push fix PRs |
| [`skills/agent-orchestrator/SKILL.md`](skills/agent-orchestrator/SKILL.md) | Architecture and conventions for working on this codebase |
| [`skills/release-notes/ao-weekly-release/SKILL.md`](skills/release-notes/ao-weekly-release/SKILL.md) | Generate weekly release notes from git history |
| [`skills/social-media/SKILL.md`](skills/social-media/SKILL.md) | Social media post generation |
See [`skills/README.md`](skills/README.md) for how to install skills into other coding agents (Cursor, Copilot, Codex, etc.).
## Plugin Standards
### Package Layout

91
skills/README.md Normal file
View File

@ -0,0 +1,91 @@
# Skills
Reusable skill documents for AI coding agents working on this repository. Each skill is a self-contained `SKILL.md` that teaches an agent how to perform a specific task.
## Available Skills
| Skill | Description |
|-------|-------------|
| [`bug-triage/`](bug-triage/SKILL.md) | Triage bugs reported in chat/issues — investigate, search duplicates, file GitHub issues, push fix PRs |
| [`agent-orchestrator/`](agent-orchestrator/SKILL.md) | Architecture and conventions for working on the agent-orchestrator codebase |
| [`release-notes/`](release-notes/ao-weekly-release/SKILL.md) | Generate weekly release notes from git history |
| [`social-media/`](social-media/SKILL.md) | Social media post generation |
## How to Use
Copy the skill into your coding agent's skill directory. The destination depends on which agent you're using:
### Claude Code
```bash
cp -r skills/bug-triage .claude/skills/bug-triage
```
Or add the full path to your `CLAUDE.md`:
```markdown
See skills/bug-triage/SKILL.md for bug triage workflow.
```
### OpenAI Codex CLI
```bash
cp -r skills/bug-triage .codex/skills/bug-triage
```
Or reference in `AGENTS.md`:
```markdown
See skills/bug-triage/SKILL.md for bug triage workflow.
```
### Cursor
Add to `.cursor/rules/` as a rule file:
```bash
cp skills/bug-triage/SKILL.md .cursor/rules/bug-triage.mdc
```
### Windsurf / Other Codeium-based agents
Add to `.windsurf/rules/`:
```bash
cp skills/bug-triage/SKILL.md .windsurf/rules/bug-triage.md
```
### GitHub Copilot
Add to `.github/copilot-instructions.md` or reference in `.github/`:
```bash
cp skills/bug-triage/SKILL.md .github/skills/bug-triage.md
```
### Gemini CLI
```bash
cp -r skills/bug-triage .gemini/skills/bug-triage
```
### Agent Orchestrator (this project)
Skills in this `skills/` directory are automatically available to agents spawned via `ao spawn`. Reference them in `AGENTS.md` or `CLAUDE.md` so agents load them at the start of a session.
## Writing a New Skill
1. Create a directory under `skills/<name>/`
2. Add a `SKILL.md` with YAML frontmatter:
```yaml
---
name: my-skill
description: One-line description of what the skill does.
trigger: When to activate this skill.
---
```
3. Write the skill body in markdown — numbered steps, code blocks, tables
4. Keep it agent-agnostic: use `gh` CLI, `git`, and standard Unix tools. Avoid tying to a specific agent framework
5. Reference it in `AGENTS.md` so spawned agents discover it

311
skills/bug-triage/SKILL.md Normal file
View File

@ -0,0 +1,311 @@
---
name: bug-triage
description: Triage bugs reported in chat/issues, search for duplicates, file or update GitHub issues with full context, and push fix PRs.
trigger: User reports a bug, or asks to triage/file an issue for a reported problem.
---
# Bug Triage Skill
Triage bugs into well-structured GitHub issues on the correct upstream repo.
## 1. Pre-flight
- **Pull latest code:** `git pull origin main`. Stale code = bad triage.
- **Target repo:** Always file on the **upstream org** (`ComposioHQ/agent-orchestrator`), not forks.
- **Record source:** chat URL, reporter name, attachments.
## 2. Gather Context
### 2a. Extract the report
| Source | How to gather |
|--------|---------------|
| **Discord/Slack thread** | Read full thread. Extract: reporter name, original description (the thread starter, not whoever tagged you), screenshots, follow-ups |
| **GitHub issue** | `gh issue view <number> --repo <repo> --json body,comments` |
| **Live observation** | Pull live state via observability tools |
### 2b. Minimum viable report gate
Before tracing code, verify the report has enough substance:
**Required (ALL):** what happened, where (page/command/feature), when (after upgrade? first time?)
**Required (2 of 4):** OS/shell/runtime, AO version (`ao --version`), reproducibility (consistent vs intermittent), reproduction steps
If insufficient, ask:
> "I'd like to triage this but need more info: (1) **What happened?** (error/behavior), (2) **Where?** (page/command), (3) **When did it start?**, (4) **How to reproduce?**"
### 2c. Local diagnostics (if bug is on same machine)
Gather everything yourself before asking the reporter:
```bash
# Environment
ao --version && node --version && echo $SHELL && uname -a
cat agent-orchestrator.yaml
cat ~/.agent-orchestrator/running.json
# Process health
pm2 status
tmux list-sessions
lsof -i :3000
# AO event log — structured timeline
ao events list --limit 50 # recent events
ao events list --session ao-5 --limit 100 # filter by session
ao events list --log-level error --since 1h # errors only
ao events search "spawn failed" # full-text search
ao events stats # counts by kind/source
# Session state files
cat ~/.agent-orchestrator/projects/*/sessions/*.json | python3 -m json.tool
```
Event kinds: `session.spawned`, `session.spawn_failed`, `session.killed`, `lifecycle.transition`, `ci.failing`, `review.pending`, `runtime.probe_failed`, `agent.process_probe_failed`, `reaction.escalated`, `lifecycle.poll_failed`. Sources: `lifecycle`, `session-manager`, `api`, `runtime`, `agent`, `reaction`.
**Try the reproduction steps.** Running the actual command is worth 100 lines of code tracing.
## 3. Investigate
### 3a. Trace the code path
**Always trace the actual code** — don't surface-level diagnose. [#1129](https://github.com/ComposioHQ/agent-orchestrator/issues/1129) looked like a simple `ao stop` issue but was actually a session lineage/cascade problem.
```bash
git fetch origin main && git log --oneline origin/main -5 # current HEAD
# Record the commit hash you're analyzing against
```
**Git archaeology** — find which commits introduced/removed specific code:
```bash
git log --oneline -S 'exact-string' -- <file>
git show <sha> -- <file> | grep -B 5 -A 10 'pattern'
```
Example: [#1391](https://github.com/ComposioHQ/agent-orchestrator/issues/1391) traced a mobile layout break to a `display: flex``display: grid` change.
**Research upstream dependencies** (xterm, node-pty, React, etc.) — check installed vs latest version, search their GitHub issues, check changelogs. Root cause is often upstream.
### 3b. Cross-platform check
AO runs on **Windows, macOS, Linux** as first-class targets. If env info indicates Windows (or is unknown), check for these patterns:
- **Path separators** — hardcoded `/` or `\` breaks cross-platform
- **Shell syntax** — PowerShell lacks `&&`, `$VAR`, `$(cat ...)`, `/dev/null`, here-docs
- **`process.platform === "win32"` inline** — must use `isWindows()` from `@aoagents/ao-core`
- **`process.kill(-pid)`** — POSIX-only; use `killProcessTree()`
- **Named pipes vs Unix sockets** — Windows uses `\\.\pipe\ao-pty-<id>`
- **`localhost`** — Windows resolves to `::1` first, causing ~21s stalls on IPv4-only servers
- **NTFS case-insensitivity** — use `pathsEqual()`, not `===`
- **ConPTY orphans** — can trigger WER dialogs if pty-host not shut down cooperatively
- **`.cmd` shim resolution** — needs `shell: true` for `PATHEXT` lookup
Key files: `packages/core/src/platform.ts`, `docs/CROSS_PLATFORM.md`, `packages/plugins/runtime-process/`, `packages/cli/src/lib/path-equality.ts`
### 3c. Stop-and-ask triggers
Stop and ask for more info if:
- **3 failed hypotheses** — traced 3 code paths, none explain it
- **Root cause is upstream** — file with upstream reference, don't guess a local fix
- **UI-only bug** and you can't screenshot — ask reporter to describe
- **Can't reproduce** — ask for different config/sequence
## 4. Search for Duplicates
Search with multiple strategies, always using `--state all` (closed bugs regress):
```bash
gh issue list --repo <repo> --state all --search "<symptom>"
gh issue list --repo <repo> --state all --search "<component-name>"
gh issue list --repo <repo> --state all --search "<error-message>"
gh pr list --repo <repo> --state all --search "<keywords>"
```
### Duplicate found → comment on existing issue
```bash
gh issue comment <number> --repo <repo> --body "$(cat <<'EOF'
## New Report
**Reported by:** @<reporter> in [chat](<url>)
**Date:** <YYYY-MM-DD> | **Checkout:** `<commit-hash>`
<context, differences from original, screenshots>
EOF
)"
```
### No duplicate → file new issue (next section)
## 5. File New Issue
### 5a. Pre-submission checklist
- [ ] Reporter attribution correct (original reporter, not who tagged you)
- [ ] Commit hash recorded
- [ ] AO version recorded
- [ ] Root cause confidence scored (see 5c)
- [ ] Related issues cross-linked
- [ ] Reproduction steps are concrete
- [ ] Screenshots uploaded with real URLs (see 5b)
### 5b. Upload screenshots
**⛔ NEVER use placeholder URLs.** Upload BEFORE creating the issue. ([#1151](https://github.com/ComposioHQ/agent-orchestrator/issues/1151) RCA on this pattern.)
```bash
SLUG="descriptive-slug"
# Create asset branch
gh api -X POST repos/<repo>/git/refs \
-f ref="refs/heads/issue-assets-${SLUG}" \
-f sha=$(git rev-parse origin/main)
# Upload (portable base64)
IMG_B64=$(base64 < /path/to/screenshot.png | tr -d '\n')
gh api -X PUT "repos/<repo>/contents/.issue-assets/${SLUG}/name.png" \
-f message="chore: upload screenshot" \
-f content="$IMG_B64" \
-f branch="issue-assets-${SLUG}"
# Use: ![screenshot](https://raw.githubusercontent.com/<repo>/issue-assets-<slug>/.issue-assets/<file>)
```
### 5c. Create the issue
```bash
gh issue create --repo <repo> --title "<title>" --body "$(cat <<'EOF'
## Bug
<summary>
**Source:** <url> | **Reported by:** @<reporter> | **Analyzed against:** `<hash>`
**Confidence:** High/Medium/Low
## Reproduction
1. <step>
## Root Cause
<file paths, line numbers, explanation>
## Fix
<suggested approach>
## Impact
- <effects>
EOF
)"
```
### 5d. Label and prioritize
```bash
gh issue edit <number> --repo <repo> --add-label "bug"
```
| Priority label | Criteria |
|----------------|----------|
| `priority: critical` | Data loss, security, system down |
| `priority: high` | Core feature broken, no workaround |
| `priority: medium` | Feature degraded, workaround exists |
| `priority: low` | Cosmetic, edge case |
**Confidence scoring** (include in issue body):
| Level | Meaning | Extra labels |
|-------|---------|-------------|
| **High** | Traced exact code path, specific lines, mechanism explained | `bug` only |
| **Medium** | Strong hypothesis but unconfirmed | `bug`, `to-explore` |
| **Low** | Can't trace, multiple conflicting theories | `bug`, `to-reproduce` |
Example: [PR #1608](https://github.com/ComposioHQ/agent-orchestrator/pull/1608) was diagnosed High as xterm v6 issue — real cause was a `=` prefix on tmux `set-option`. Should have been Medium.
**All available labels:** `priority: critical/high/medium/low`, `bug`, `enhancement`, `good-first-issue`, `to-reproduce`, `to-explore`. No others (no `p0`, `p1`, etc.).
### 5e. Cross-link related issues
Search by subsystem and add a `## Related` section to the issue body:
```
## Related
- [#1020](url) — stale session blocking ao start (same subsystem)
- [#1035](url) — same race condition
```
### 5f. Push a fix PR (always attempt)
- **Trivial fix:** Push immediately.
- **Complex fix:** Note in issue, suggest spawning an agent.
- **Unclear fix:** Don't push a guess. Document and flag.
```bash
OLD_STRING='<old>' NEW_STRING='<new>' \
python3 skills/bug-triage/scripts/push_fix_to_github.py \
<owner/repo> fix/slug path/to/file.tsx \
"fix(scope): commit msg" "fix(scope): PR title" \
"Fixes #<n>
## Summary
<what changed>
## Test
<how to verify>"
```
The script reads from GitHub API, applies one replacement, pushes, opens PR — no local checkout needed. **Verify `OLD_STRING` matches GitHub first:** `gh api repos/<repo>/contents/<path>?ref=main -q '.content' | python3 -c "import base64,sys; sys.stdout.buffer.write(base64.b64decode(sys.stdin.read()))"`
**Multiple edits to same file:** The push script does one replacement per run. For multiple changes, use a Python script to read from the branch (`gh api` + `base64.b64decode`), apply all replacements, then push via `gh api -X PUT` with the updated SHA.
### 5g. Report back
Issue URL, PR URL (if created), labels, root cause summary, whether fix agent was suggested.
---
## Appendix
### A. Subsystem Quick Reference
| Subsystem | Collect | Key files |
|-----------|---------|-----------|
| **CLI** (`ao start/stop/spawn`) | Config YAML, install method, version, OS | `packages/cli/src/commands/` |
| **Web UI** | Screenshot, browser, viewport | `packages/web/src/components/`, `globals.css` |
| **Terminal** | Runtime type, tmux version, shell | `DirectTerminal.tsx`, `useXtermTerminal.ts` |
| **Lifecycle** | State transitions, session IDs | `core/src/lifecycle-manager.ts`, `core/src/lifecycle-state.ts` |
| **Sessions** | Session ID, spawn config, runtime | `core/src/session-manager.ts` |
| **Plugins** | Plugin name, agent version | `packages/plugins/<agent>/` |
| **Config** | YAML contents, project path | `packages/core/src/config.ts` |
**Misrouting patterns:**
- Terminal bugs → tmux (runtime-tmux) vs xterm (web) vs PTY (runtime-process/Windows). Trace where bytes flow.
- "Session stuck" → lifecycle state machine vs agent process vs runtime connection.
- "Config not saving" → config loading (c12) vs project registration (running-state.ts) vs YAML write (permissions).
### B. Remote Code Inspection (no local clone)
```bash
gh api repos/{owner}/{repo}/git/trees/main?recursive=1 --jq '.tree[].path' # list files
gh api repos/{owner}/{repo}/contents/{path} --jq '.content' | python3 -c "import base64,sys; sys.stdout.buffer.write(base64.b64decode(sys.stdin.read()))" # read file
gh search code "term" --repo {owner}/{repo} --json path --jq '.[].path' # search code
gh api "repos/{owner}/{repo}/commits?path={path}&per_page=10" --jq '.[] | "\(.sha[0:8]) \(.commit.message | split("\n")[0])"' # file history
```
### C. NPM Package Regression Diffing
Diff **published** packages (not local builds) when regression follows an upgrade:
```bash
mkdir -p /tmp/ao-diff/{v1,v2}
curl -sL https://registry.npmjs.org/@scope/pkg/-/pkg-OLD.tgz | tar xz -C /tmp/ao-diff/v1
curl -sL https://registry.npmjs.org/@scope/pkg/-/pkg-NEW.tgz | tar xz -C /tmp/ao-diff/v2
diff -rq /tmp/ao-diff/v1/package/ /tmp/ao-diff/v2/package/
```
Example: [PR #1608](https://github.com/ComposioHQ/agent-orchestrator/pull/1608) — source analysis led to wrong theories, npm diff showed the only change was a `=` prefix on tmux `set-option`.
## Formatting Rules
- **Linkify all issue/PR refs:** `[#123](https://github.com/ComposioHQ/agent-orchestrator/issues/123)`, `[PR #456](url)`. Never bare `#123`.
## Pitfalls
- **Reporter ≠ person who tagged you.** Always attribute to the original reporter.
- **Record the commit hash** you analyzed — code changes fast.
- **GitHub issue is mandatory** — every triaged bug gets one, even if fix is trivial.
- **`gh api --jq .content` truncates large files** (>~100KB). Use local git instead.
- **Push script arg limits** — long commit messages hit `OSError: Argument list too long`. Use a Python script with JSON payloads instead.
- **`OLD_STRING` must match GitHub byte-for-byte** — local code may differ from `origin/main`.
- **New fields on shared TS interfaces MUST be optional** (`field?: Type`). Downstream `Partial<X>` spreads break on required fields. Example: [PR #1523](https://github.com/ComposioHQ/agent-orchestrator/pull/1523).

View File

@ -0,0 +1,121 @@
#!/usr/bin/env python3
"""
Push a file fix to GitHub via API and create a PR.
Usage: python3 push_fix_to_github.py <repo> <branch-name> <file-path> <commit-message> <pr-title> <pr-body>
Reads the original file content from GitHub (default branch), applies a sed-like
replacement using OLD_STRING / NEW_STRING env vars, and pushes via GitHub API.
Environment variables:
OLD_STRING - The exact string to find in the file (required)
NEW_STRING - The replacement string (set to empty string to delete) (required as env var)
BASE_SHA - Override the base commit SHA (optional, defaults to default branch HEAD)
BASE_BRANCH - Override the base branch name (optional, defaults to "main")
Example:
OLD_STRING='<td className="foo">{bar}</td>' \
NEW_STRING='<td className="foo"><a href="#">{bar}</a></td>' \
python3 push_fix_to_github.py ComposioHQ/agent-orchestrator fix/branch packages/web/src/File.tsx \
"fix: description" "fix: title" "Fixes #123"
"""
import sys, os, json, subprocess, base64
def run_gh(args, check=True):
"""Run a gh API command and return parsed JSON."""
cmd = ["gh", "api"] + args
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
if check and result.returncode != 0:
print(f"ERROR: gh api failed: {result.stderr}", file=sys.stderr)
sys.exit(1)
try:
return json.loads(result.stdout) if result.stdout.strip() else {}
except json.JSONDecodeError:
if check:
print(f"ERROR: Invalid JSON: {result.stdout[:300]}", file=sys.stderr)
sys.exit(1)
return {}
if __name__ == "__main__":
if len(sys.argv) < 7:
print("Usage: push_fix_to_github.py <repo> <branch> <file-path> <commit-msg> <pr-title> <pr-body>", file=sys.stderr)
sys.exit(1)
repo = sys.argv[1]
branch = sys.argv[2]
file_path = sys.argv[3]
commit_msg = sys.argv[4]
pr_title = sys.argv[5]
pr_body = sys.argv[6]
base_branch = os.environ.get("BASE_BRANCH", "main")
old_string = os.environ.get("OLD_STRING", "")
new_string = os.environ.get("NEW_STRING")
if not old_string:
print("ERROR: OLD_STRING env var is required", file=sys.stderr)
sys.exit(1)
if new_string is None:
print("ERROR: NEW_STRING env var is required (set to empty string to delete)", file=sys.stderr)
sys.exit(1)
# 1. Get base HEAD SHA first
base_sha = os.environ.get("BASE_SHA")
if not base_sha:
ref_data = run_gh([f"repos/{repo}/git/ref/heads/{base_branch}"])
base_sha = ref_data["object"]["sha"]
# 2. Create branch from base SHA (before fetching file to avoid SHA race)
print(f"Creating branch {branch} from {base_branch} ({base_sha[:8]})...")
run_gh([
"-X", "POST", f"repos/{repo}/git/refs",
"-f", f"ref=refs/heads/{branch}",
"-f", f"sha={base_sha}"
], check=False)
# 3. Fetch file content from the new branch (avoids SHA mismatch)
print(f"Fetching {file_path} from {repo} (branch: {branch})...")
file_data = run_gh([f"repos/{repo}/contents/{file_path}?ref={branch}"])
file_sha = file_data["sha"]
decoded_content = base64.b64decode(file_data["content"]).decode("utf-8")
print(f"File SHA: {file_sha}")
# 4. Apply replacement
if old_string not in decoded_content:
print(f"ERROR: OLD_STRING not found in file!", file=sys.stderr)
print(f"Looking for:\n{old_string}", file=sys.stderr)
sys.exit(1)
match_count = decoded_content.count(old_string)
if match_count > 1:
print(f"WARNING: OLD_STRING found {match_count} times — replacing only the first occurrence.", file=sys.stderr)
new_content = decoded_content.replace(old_string, new_string, 1)
encoded = base64.b64encode(new_content.encode("utf-8")).decode("ascii")
# 5. Push file to branch
print(f"Pushing updated file...")
result = run_gh([
"-X", "PUT", f"repos/{repo}/contents/{file_path}",
"-f", f"message={commit_msg}",
"-f", f"content={encoded}",
"-f", f"sha={file_sha}",
"-f", f"branch={branch}"
])
# 6. Create PR
print(f"Creating PR...")
pr_result = run_gh([
"-X", "POST", f"repos/{repo}/pulls",
"-f", f"title={pr_title}",
"-f", f"body={pr_body}",
"-f", f"head={branch}",
"-f", f"base={base_branch}"
])
pr_url = pr_result.get("html_url", "unknown")
pr_number = pr_result.get("number", "?")
print(f"\n✅ PR #{pr_number}: {pr_url}")