Skip to content

Commit 87eb7f0

Browse files
zparnoldclaude
andcommitted
Add upstream sync infrastructure (check script, merge guide)
- scripts/check-upstream-sync.sh: daily sync checker (matches OpenHands pattern) - docs/upstream-merge-guide.md: 12-step merge procedure - .gitignore: exclude .last-upstream-sync marker file Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 96dd776 commit 87eb7f0

File tree

3 files changed

+302
-0
lines changed

3 files changed

+302
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,3 +211,6 @@ agent-sdk.workspace.code-workspace
211211

212212
# Integration test outputs
213213
tests/integration/outputs/
214+
215+
# Upstream sync marker
216+
.last-upstream-sync

docs/upstream-merge-guide.md

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
# Upstream Merge Guide — software-agent-sdk
2+
3+
Procedure for merging upstream changes from `OpenHands/software-agent-sdk` into our fork.
4+
5+
## Overview
6+
7+
| Item | Value |
8+
|------|-------|
9+
| **Upstream repo** | `https://github.com/OpenHands/software-agent-sdk` |
10+
| **Our fork** | `https://github.com/zparnold/software-agent-sdk` |
11+
| **Working branch** | `main` |
12+
| **Custom commits** | ~7 (see list below) |
13+
14+
Unlike the OpenHands repo (which has a separate `self-hosted` branch), we work directly on `main` with custom commits on top of upstream.
15+
16+
## Custom Commits to Preserve
17+
18+
These commits contain our self-hosted customizations and must survive every merge:
19+
20+
1. **`198e2a9`**`feat(docker): add entrypoint script to handle CA certs and argv stripping`
21+
2. **`88859a8`**`feat: configure VSCode server-base-path for proxy support`
22+
3. **`d1b8c6f`**`fix: serialize webhook events with mode='json' to include kind field`
23+
4. **`a2b4f23`**`Optimize count_events to avoid full iteration when no filters applied`
24+
5. **`a2140ea`**`feat: add org-level shared skill registry support`
25+
6. **`e1fd801`**`Add auth_header and branch support to agent-server org skills loading`
26+
7. **`96dd776`**`Support microagents/ directory in load_org_skills for backwards compat`
27+
28+
Key files touched by custom commits (high conflict risk):
29+
- `openhands-agent-server/openhands/agent_server/docker/entrypoint.sh`
30+
- `openhands-agent-server/openhands/agent_server/docker/Dockerfile`
31+
- `openhands-agent-server/openhands/agent_server/vscode_service.py`
32+
- `openhands-agent-server/openhands/agent_server/event_service.py`
33+
- `openhands-agent-server/openhands/agent_server/conversation_service.py`
34+
- `openhands-agent-server/openhands/agent_server/skills_service.py`
35+
- `openhands-agent-server/openhands/agent_server/skills_router.py`
36+
37+
## Pre-Merge: Check Status
38+
39+
```bash
40+
# See how far behind we are
41+
./scripts/check-upstream-sync.sh
42+
43+
# Or manually:
44+
git fetch upstream
45+
git rev-list --count main..upstream/main # commits behind
46+
git rev-list --count upstream/main..main # custom commits ahead
47+
```
48+
49+
## Merge Procedure
50+
51+
### Step 1: Fetch upstream
52+
53+
```bash
54+
cd /home/zach/development/claude_workspace/software-agent-sdk
55+
git fetch upstream
56+
```
57+
58+
### Step 2: Create a merge branch
59+
60+
Work on a disposable branch so `main` stays clean until the merge is verified.
61+
62+
```bash
63+
git checkout main
64+
git checkout -b merge/upstream-$(date +%Y-%m-%d)
65+
```
66+
67+
### Step 3: Merge upstream/main
68+
69+
```bash
70+
git merge upstream/main --no-ff -m "Merge upstream/main into main ($(date +%Y-%m-%d))"
71+
```
72+
73+
### Step 4: Resolve conflicts
74+
75+
Conflicts will typically occur in files touched by our custom commits (listed above). For each conflict:
76+
77+
1. **Our custom files** (entrypoint.sh, Dockerfile, vscode_service.py, skills_service.py, etc.):
78+
Accept upstream changes but re-apply our customizations on top. Use `git diff upstream/main..main -- <file>` to see what we changed.
79+
80+
2. **Lock file** (`uv.lock`):
81+
Delete and regenerate:
82+
```bash
83+
rm uv.lock
84+
uv lock
85+
```
86+
87+
3. **CI/CD files** (`.github/workflows/`):
88+
Generally accept upstream unless we have specific CI customizations.
89+
90+
4. **Tests**:
91+
Accept upstream. If our custom code breaks upstream tests, fix the tests after merge.
92+
93+
### Step 5: Regenerate lock file
94+
95+
```bash
96+
uv lock
97+
```
98+
99+
### Step 6: Verify build
100+
101+
```bash
102+
make build
103+
```
104+
105+
### Step 7: Run tests
106+
107+
```bash
108+
uv run pytest tests/ -x --timeout=30
109+
```
110+
111+
### Step 8: Lint
112+
113+
```bash
114+
make lint
115+
```
116+
117+
### Step 9: Verify custom features
118+
119+
Check that our customizations still work:
120+
121+
- [ ] CA cert handling in entrypoint.sh
122+
- [ ] VSCode server-base-path proxy support
123+
- [ ] Webhook event serialization (kind field present)
124+
- [ ] count_events optimization
125+
- [ ] Org-level skill registry loads from GitHub
126+
- [ ] Auth header passed through for skill loading
127+
- [ ] microagents/ directory backwards compatibility
128+
129+
### Step 10: Complete the merge
130+
131+
```bash
132+
git checkout main
133+
git merge merge/upstream-$(date +%Y-%m-%d) --no-ff
134+
```
135+
136+
### Step 11: Mark sync point
137+
138+
```bash
139+
./scripts/check-upstream-sync.sh --mark
140+
```
141+
142+
### Step 12: Push and deploy
143+
144+
```bash
145+
git push origin main
146+
```
147+
148+
Then rebuild and deploy the agent-server image as needed.
149+
150+
## Rollback
151+
152+
If something goes wrong after merging to `main`:
153+
154+
```bash
155+
# Find the commit before the merge
156+
git log --oneline -5
157+
158+
# Reset to pre-merge state
159+
git reset --hard <pre-merge-sha>
160+
git push origin main --force-with-lease
161+
```
162+
163+
## Keeping This Guide Updated
164+
165+
After each merge, update the "Custom Commits to Preserve" section if new custom commits were added. Run `git log --oneline upstream/main..main` to get the current list.

scripts/check-upstream-sync.sh

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
#!/bin/bash
2+
# check-upstream-sync.sh
3+
# Checks if main branch is behind upstream/main and reports the delta.
4+
# Run daily via cron to stay aware of upstream changes.
5+
#
6+
# Uses a marker file (.last-upstream-sync) to track the last synced upstream commit SHA.
7+
# After a merge, run: ./scripts/check-upstream-sync.sh --mark
8+
# to update the marker to the current upstream/main HEAD.
9+
#
10+
# Usage: ./scripts/check-upstream-sync.sh [--quiet|--mark]
11+
# --quiet: Only output if there are new commits (for cron notifications)
12+
# --mark: Record current upstream/main HEAD as the last-synced point
13+
14+
set -uo pipefail
15+
trap "" PIPE
16+
17+
REPO_DIR="/home/zach/development/claude_workspace/software-agent-sdk"
18+
UPSTREAM_REMOTE="upstream"
19+
UPSTREAM_BRANCH="main"
20+
LOCAL_BRANCH="main"
21+
MARKER_FILE="${REPO_DIR}/.last-upstream-sync"
22+
ARG="${1:-}"
23+
24+
cd "$REPO_DIR"
25+
26+
# Ensure upstream remote exists
27+
if ! git remote | grep -q "^${UPSTREAM_REMOTE}$"; then
28+
echo "Adding upstream remote..."
29+
git remote add "$UPSTREAM_REMOTE" "https://github.com/OpenHands/software-agent-sdk.git"
30+
fi
31+
32+
# Fetch upstream (quietly)
33+
git fetch "$UPSTREAM_REMOTE" --quiet 2>/dev/null
34+
35+
# Handle --mark: save current upstream HEAD as synced point
36+
if [ "$ARG" = "--mark" ]; then
37+
UPSTREAM_HEAD=$(git rev-parse "${UPSTREAM_REMOTE}/${UPSTREAM_BRANCH}")
38+
echo "$UPSTREAM_HEAD" > "$MARKER_FILE"
39+
echo "Marked $(echo "$UPSTREAM_HEAD" | head -c 9) as last synced upstream commit"
40+
exit 0
41+
fi
42+
43+
# Determine the base commit to compare from
44+
if [ -f "$MARKER_FILE" ]; then
45+
SYNC_BASE=$(cat "$MARKER_FILE" | tr -d '[:space:]')
46+
# Verify the commit still exists
47+
if ! git cat-file -e "$SYNC_BASE" 2>/dev/null; then
48+
echo "WARNING: Marker commit $SYNC_BASE not found, falling back to merge-base"
49+
SYNC_BASE=""
50+
fi
51+
fi
52+
53+
if [ -z "${SYNC_BASE:-}" ]; then
54+
# Fallback: use merge-base (works for real merges, not squash merges)
55+
SYNC_BASE=$(git merge-base "${LOCAL_BRANCH}" "${UPSTREAM_REMOTE}/${UPSTREAM_BRANCH}" 2>/dev/null || echo "")
56+
if [ -z "$SYNC_BASE" ]; then
57+
echo "ERROR: Could not find merge base between $LOCAL_BRANCH and $UPSTREAM_REMOTE/$UPSTREAM_BRANCH"
58+
exit 1
59+
fi
60+
fi
61+
62+
BEHIND=$(git rev-list --count "${SYNC_BASE}..${UPSTREAM_REMOTE}/${UPSTREAM_BRANCH}" 2>/dev/null || echo "0")
63+
64+
if [ "$BEHIND" = "0" ]; then
65+
if [ "$ARG" != "--quiet" ]; then
66+
echo "$(date '+%Y-%m-%d %H:%M') | main is up to date with upstream/main"
67+
fi
68+
exit 0
69+
fi
70+
71+
# Get commit summaries
72+
echo "=========================================="
73+
echo " Upstream Sync Report - $(date '+%Y-%m-%d %H:%M')"
74+
echo "=========================================="
75+
echo ""
76+
echo "main is $BEHIND commit(s) behind upstream/main"
77+
echo "(since $(echo "$SYNC_BASE" | head -c 9))"
78+
echo ""
79+
80+
# Show custom (ahead) commits
81+
AHEAD=$(git rev-list --count "${UPSTREAM_REMOTE}/${UPSTREAM_BRANCH}..${LOCAL_BRANCH}" 2>/dev/null || echo "0")
82+
if [ "$AHEAD" -gt 0 ]; then
83+
echo "Custom commits (ahead of upstream): $AHEAD"
84+
git log --oneline --no-merges "${UPSTREAM_REMOTE}/${UPSTREAM_BRANCH}..${LOCAL_BRANCH}"
85+
echo ""
86+
fi
87+
88+
echo "New upstream commits:"
89+
echo "---"
90+
git log --oneline --no-merges "${SYNC_BASE}..${UPSTREAM_REMOTE}/${UPSTREAM_BRANCH}" | head -30
91+
if [ "$BEHIND" -gt 30 ]; then
92+
echo "... and $((BEHIND - 30)) more"
93+
fi
94+
echo ""
95+
96+
# Categorize by area
97+
RANGE="${SYNC_BASE}..${UPSTREAM_REMOTE}/${UPSTREAM_BRANCH}"
98+
SDK_COUNT=$(git log --oneline --no-merges "$RANGE" -- 'openhands-sdk/' | wc -l)
99+
TOOLS_COUNT=$(git log --oneline --no-merges "$RANGE" -- 'openhands-tools/' | wc -l)
100+
SERVER_COUNT=$(git log --oneline --no-merges "$RANGE" -- 'openhands-agent-server/' | wc -l)
101+
WORKSPACE_COUNT=$(git log --oneline --no-merges "$RANGE" -- 'openhands-workspace/' | wc -l)
102+
CI_COUNT=$(git log --oneline --no-merges "$RANGE" -- '.github/' | wc -l)
103+
TESTS_COUNT=$(git log --oneline --no-merges "$RANGE" -- 'tests/' | wc -l)
104+
105+
echo "Breakdown:"
106+
echo " SDK: $SDK_COUNT"
107+
echo " Tools: $TOOLS_COUNT"
108+
echo " Agent Server: $SERVER_COUNT"
109+
echo " Workspace: $WORKSPACE_COUNT"
110+
echo " CI/CD: $CI_COUNT"
111+
echo " Tests: $TESTS_COUNT"
112+
echo ""
113+
114+
# Files changed summary
115+
echo "Files changed: $(git diff --stat "${SYNC_BASE}..${UPSTREAM_REMOTE}/${UPSTREAM_BRANCH}" | tail -1)"
116+
echo ""
117+
118+
# Potential conflict check
119+
CUSTOM_FILES=$(git diff --name-only "${UPSTREAM_REMOTE}/${UPSTREAM_BRANCH}..${LOCAL_BRANCH}" 2>/dev/null)
120+
UPSTREAM_FILES=$(git diff --name-only "${SYNC_BASE}..${UPSTREAM_REMOTE}/${UPSTREAM_BRANCH}" 2>/dev/null)
121+
BOTH_MODIFIED=$(comm -12 <(echo "$CUSTOM_FILES" | sort) <(echo "$UPSTREAM_FILES" | sort))
122+
CONFLICT_COUNT=$(echo "$BOTH_MODIFIED" | grep -c . || true)
123+
124+
if [ "$CONFLICT_COUNT" -gt 0 ]; then
125+
echo "Potential conflict files ($CONFLICT_COUNT files modified on both sides):"
126+
echo "$BOTH_MODIFIED" | head -20
127+
if [ "$CONFLICT_COUNT" -gt 20 ]; then
128+
echo "... and $((CONFLICT_COUNT - 20)) more"
129+
fi
130+
echo ""
131+
fi
132+
133+
echo "Run the merge guide: docs/upstream-merge-guide.md"
134+
echo "=========================================="

0 commit comments

Comments
 (0)