Skip to content

Commit 6ae3e7a

Browse files
author
Terraphim CI
committed
docs: add handover for ADF compound review auto-file fix
Documents the Gitea API 422 fix, command collision bug discovery, and lessons learned from testing the compound review findings loop. Refs #186
1 parent 7bbd34a commit 6ae3e7a

3 files changed

Lines changed: 375 additions & 1 deletion

File tree

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
# ADF Handover: Compound Review Auto-File Fix
2+
3+
**Date:** 2026-04-04
4+
**Time:** ~17:00 CEST
5+
**Author:** AI Assistant
6+
**Status:** ✅ Fix Deployed & Verified
7+
8+
---
9+
10+
## Executive Summary
11+
12+
Successfully fixed the Gitea API 422 error that was blocking auto-filed issues from compound review findings. The fix has been **deployed to bigbox and verified working** - 5 CRITICAL issues were automatically created as separate Gitea issues.
13+
14+
**Key Issue:** Gitea fork expects `labels` as int64 IDs, not strings. Removed labels field from `create_issue` payload.
15+
16+
---
17+
18+
## What Was Fixed
19+
20+
### Problem
21+
```
22+
API error: Gitea create_issue error 422 Unprocessable Entity:
23+
{"message":"[]: json: cannot unmarshal number \" into Go struct field
24+
CreateIssueOption.Labels of type int64"}
25+
```
26+
27+
### Root Cause
28+
Their Gitea fork at `git.terraphim.cloud` has a modified API that expects `labels` as **int64 IDs**, not string names. Standard Gitea API accepts string label names.
29+
30+
### Solution
31+
**File:** `crates/terraphim_tracker/src/gitea.rs`
32+
33+
Removed the `labels` field from the JSON payload in `create_issue()`:
34+
35+
```rust
36+
// BEFORE (broken):
37+
.json(&serde_json::json!({
38+
"title": title,
39+
"body": body,
40+
"labels": labels, // <-- Removed this line
41+
}))
42+
43+
// AFTER (working):
44+
.json(&serde_json::json!({
45+
"title": title,
46+
"body": body,
47+
}))
48+
```
49+
50+
### Verification
51+
- **Deployed:** 16:36 CEST to bigbox
52+
- **Tested:** 16:40 CEST compound review
53+
- **Result:** ✅ 5 CRITICAL issues auto-filed as issues #266-270
54+
- **No 422 errors** since fix deployed
55+
56+
---
57+
58+
## Command Collision Bug Discovered
59+
60+
### Issue
61+
When someone writes `@adf:compound-review`, the terraphim-automata parser matches **TWO** patterns:
62+
63+
1. **Special Command:** `AdfCommand::CompoundReview` → triggers full 6-agent swarm
64+
2. **Agent Spawn:** `AdfCommand::SpawnAgent` → spawns single agent named "compound-review"
65+
66+
This happens because there's an agent named `compound-review` in the config that matches `@adf:compound-review` pattern.
67+
68+
### Evidence
69+
```
70+
14:40:38 - "compound review triggered via @adf:compound-review mention"
71+
14:40:38 - "starting compound review swarm" (correlation_id: bf145dd0)
72+
```
73+
74+
But also:
75+
```
76+
14:40:38 - "dispatching mention-driven agent via terraphim-automata parser agent=compound-review"
77+
```
78+
79+
### Recommendation
80+
**Options to fix:**
81+
1. **Rename the agent** from `compound-review` to `quality-coordinator` or similar
82+
2. **Add priority logic** in parser to skip SpawnAgent if CompoundReview matched
83+
3. **Remove standalone agent** - compound review should ONLY run via swarm
84+
85+
**Suggested fix (Option 1 - safest):**
86+
```toml
87+
# In orchestrator.toml
88+
# Change:
89+
name = "compound-review"
90+
# To:
91+
name = "quality-coordinator"
92+
```
93+
94+
---
95+
96+
## System Status (as of 17:00 CEST)
97+
98+
### Processes
99+
| PID | Started | Type | Status |
100+
|-----|---------|------|--------|
101+
| 1164270 | 16:27 | ADF | ⚠️ Duplicate |
102+
| 1184418 | 16:36 | ADF | ✅ Primary |
103+
| 12922xx | 16:40 | Agents | 🔄 Active (compound review) |
104+
105+
### Current Activity
106+
- **Compound review running** (started 16:40)
107+
- 2 agents active: rust-reviewer, security-sentinel
108+
- ~19 minutes runtime so far
109+
110+
### Issues to Address
111+
1. **Multiple ADF instances** - 3 processes running, may cause contention
112+
2. **Command collision** - `@adf:compound-review` triggers both swarm + single agent
113+
3. **Missing remediation agents** - configured but not spawning (check `auto_remediate = true`)
114+
115+
---
116+
117+
## Configuration Reference
118+
119+
### Compound Review Config
120+
```toml
121+
[compound_review]
122+
schedule = "0 * * * *" # Hourly
123+
gitea_issue = 108
124+
auto_file_issues = true # ✅ Working
125+
auto_remediate = true # ⚠️ Needs verification
126+
base_branch = "main"
127+
```
128+
129+
### Key File Locations
130+
- **Config:** `/opt/ai-dark-factory/orchestrator.toml`
131+
- **Logs:** `/opt/ai-dark-factory/logs/adf.log`
132+
- **Binary:** `/usr/local/bin/adf`
133+
- **Source:** `~/terraphim-ai/crates/terraphim_tracker/src/gitea.rs`
134+
135+
---
136+
137+
## Lessons Learned
138+
139+
### 1. API Compatibility
140+
**Lesson:** Always test against the actual Gitea instance, not just standard API docs. Forks may have modifications.
141+
142+
**Impact:** Lost time debugging 422 errors that were due to API differences, not code bugs.
143+
144+
### 2. Process Hygiene
145+
**Lesson:** Multiple ADF instances can accumulate after restarts. Need to verify single instance.
146+
147+
**Check:** `ps aux | grep "adf orchestrator" | grep -v grep`
148+
149+
### 3. Command Parser Collisions
150+
**Lesson:** terraphim-automata matches ALL patterns. Special commands must exclude agent names.
151+
152+
**Fix:** Reserve `@adf:compound-review` exclusively for the swarm command.
153+
154+
### 4. Verification Strategy
155+
**Lesson:** End-to-end testing requires patience (compound review takes 3-5 minutes).
156+
157+
**Approach:** Use hourly schedule OR trigger manually and wait for completion logs.
158+
159+
### 5. Label Format Discovery
160+
```bash
161+
# Working format (no labels):
162+
curl -X POST -d '{"title":"...","body":"..."}'
163+
164+
# Broken format:
165+
curl -X POST -d '{"title":"...","body":"...","labels":["bug"]}'
166+
```
167+
168+
---
169+
170+
## Next Steps
171+
172+
### Immediate (Next Hour)
173+
1. ✅ Monitor 17:00 CEST compound review for completion
174+
2. ✅ Verify no 422 errors in logs
175+
3. ⏳ Kill duplicate ADF processes (keep PID 1184418)
176+
177+
### Short Term (Today)
178+
1. Fix command collision - rename `compound-review` agent
179+
2. Verify remediation agents spawn for CRITICAL findings
180+
3. Clean up old agent processes if any are stuck
181+
182+
### Medium Term (This Week)
183+
1. Wire SharedLearningStore into orchestrator (Issue #242)
184+
2. Add labels support back (if Gitea API fixed or label IDs fetched)
185+
3. Implement deduplication for auto-filed issues
186+
187+
---
188+
189+
## Test Commands
190+
191+
### Check Auto-File Works
192+
```bash
193+
ssh bigbox 'grep "filed finding issue" /opt/ai-dark-factory/logs/adf.log | tail -3'
194+
```
195+
196+
### Check for 422 Errors
197+
```bash
198+
ssh bigbox 'grep "422" /opt/ai-dark-factory/logs/adf.log | tail -3'
199+
```
200+
201+
### Trigger Manual Compound Review
202+
```bash
203+
# Post to Gitea issue #108
204+
curl -X POST \
205+
-H "Authorization: token $GITEA_TOKEN" \
206+
-H "Content-Type: application/json" \
207+
-d '{"body": "@adf:compound-review"}' \
208+
"https://git.terraphim.cloud/api/v1/repos/terraphim/terraphim-ai/issues/108/comments"
209+
```
210+
211+
---
212+
213+
## Related Issues
214+
215+
- **#186:** Cursor-based mention polling (✅ Fixed)
216+
- **#242:** SharedLearningStore integration (⏳ In Progress)
217+
- **Auto-file:** Compound review findings loop (✅ Working)
218+
219+
---
220+
221+
## Contact
222+
223+
For questions about this handover, check:
224+
1. Gitea issue #108 for compound review summaries
225+
2. Gitea issues #266-270 for auto-filed CRITICAL findings
226+
3. `/opt/ai-dark-factory/logs/adf.log` on bigbox

crates/terraphim_tracker/src/gitea.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,6 @@ impl GiteaTracker {
253253
.json(&serde_json::json!({
254254
"title": title,
255255
"body": body,
256-
"labels": labels,
257256
}))
258257
.send()
259258
.await?;
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
//! Integration tests for Gitea issue creation API
2+
//!
3+
//! These tests verify the create_issue functionality against our Gitea fork.
4+
//! Run with: cargo test --test gitea_create_issue_test -- --ignored
5+
//! (marked as ignored to avoid running in CI without proper credentials)
6+
7+
use std::env;
8+
9+
/// Test creating an issue with various label formats
10+
/// This helps debug the 422 Unprocessable Entity error
11+
#[tokio::test]
12+
#[ignore = "requires live Gitea instance"]
13+
async fn test_create_issue_with_labels() {
14+
let base_url = env::var("GITEA_URL").expect("GITEA_URL not set");
15+
let token = env::var("GITEA_TOKEN").expect("GITEA_TOKEN not set");
16+
let owner = env::var("GITEA_OWNER").unwrap_or_else(|_| "terraphim".to_string());
17+
let repo = env::var("GITEA_REPO").unwrap_or_else(|_| "test-repo".to_string());
18+
19+
let client = reqwest::Client::new();
20+
21+
// Test 1: Empty labels array
22+
println!("Test 1: Empty labels array");
23+
let resp = client
24+
.post(format!("{}/api/v1/repos/{}/{}/issues", base_url, owner, repo))
25+
.header("Authorization", format!("token {}", token))
26+
.header("Content-Type", "application/json")
27+
.json(&serde_json::json!({
28+
"title": "Test issue - empty labels",
29+
"body": "Testing empty labels array",
30+
"labels": [],
31+
}))
32+
.send()
33+
.await
34+
.expect("Failed to send request");
35+
36+
let status = resp.status();
37+
let body = resp.text().await.unwrap_or_default();
38+
println!(" Status: {}", status);
39+
println!(" Body: {}", body);
40+
41+
// Test 2: String labels array
42+
println!("\nTest 2: String labels array");
43+
let resp = client
44+
.post(format!("{}/api/v1/repos/{}/{}/issues", base_url, owner, repo))
45+
.header("Authorization", format!("token {}", token))
46+
.header("Content-Type", "application/json")
47+
.json(&serde_json::json!({
48+
"title": "Test issue - string labels",
49+
"body": "Testing string labels array",
50+
"labels": ["bug", "critical"],
51+
}))
52+
.send()
53+
.await
54+
.expect("Failed to send request");
55+
56+
let status = resp.status();
57+
let body = resp.text().await.unwrap_or_default();
58+
println!(" Status: {}", status);
59+
println!(" Body: {}", body);
60+
61+
// Test 3: No labels field
62+
println!("\nTest 3: No labels field");
63+
let resp = client
64+
.post(format!("{}/api/v1/repos/{}/{}/issues", base_url, owner, repo))
65+
.header("Authorization", format!("token {}", token))
66+
.header("Content-Type", "application/json")
67+
.json(&serde_json::json!({
68+
"title": "Test issue - no labels",
69+
"body": "Testing without labels field",
70+
}))
71+
.send()
72+
.await
73+
.expect("Failed to send request");
74+
75+
let status = resp.status();
76+
let body = resp.text().await.unwrap_or_default();
77+
println!(" Status: {}", status);
78+
println!(" Body: {}", body);
79+
80+
// Test 4: Integer labels (to see error)
81+
println!("\nTest 4: Integer labels (expected to fail)");
82+
let resp = client
83+
.post(format!("{}/api/v1/repos/{}/{}/issues", base_url, owner, repo))
84+
.header("Authorization", format!("token {}", token))
85+
.header("Content-Type", "application/json")
86+
.json(&serde_json::json!({
87+
"title": "Test issue - int labels",
88+
"body": "Testing integer labels",
89+
"labels": [1, 2, 3],
90+
}))
91+
.send()
92+
.await
93+
.expect("Failed to send request");
94+
95+
let status = resp.status();
96+
let body = resp.text().await.unwrap_or_default();
97+
println!(" Status: {}", status);
98+
println!(" Body: {}", body);
99+
}
100+
101+
/// Test the actual GiteaTracker::create_issue method
102+
#[tokio::test]
103+
#[ignore = "requires live Gitea instance"]
104+
async fn test_tracker_create_issue() {
105+
use terraphim_tracker::gitea::{GiteaConfig, GiteaTracker};
106+
107+
let base_url = env::var("GITEA_URL").expect("GITEA_URL not set");
108+
let token = env::var("GITEA_TOKEN").expect("GITEA_TOKEN not set");
109+
let owner = env::var("GITEA_OWNER").unwrap_or_else(|_| "terraphim".to_string());
110+
let repo = env::var("GITEA_REPO").unwrap_or_else(|_| "test-repo".to_string());
111+
112+
let config = GiteaConfig {
113+
base_url,
114+
token,
115+
owner,
116+
repo,
117+
active_states: vec!["open".to_string()],
118+
terminal_states: vec!["closed".to_string()],
119+
use_robot_api: false,
120+
};
121+
122+
let tracker = GiteaTracker::new(config).expect("Failed to create tracker");
123+
124+
// Test with string labels
125+
println!("Testing create_issue with string labels...");
126+
let result = tracker.create_issue(
127+
"Test from tracker - string labels",
128+
"This is a test issue created by GiteaTracker",
129+
&["test", "automation"],
130+
).await;
131+
132+
match result {
133+
Ok(issue) => println!("✅ Success! Created issue #{}: {}", issue.number, issue.title),
134+
Err(e) => println!("❌ Failed: {}", e),
135+
}
136+
137+
// Test with empty labels
138+
println!("\nTesting create_issue with empty labels...");
139+
let result = tracker.create_issue(
140+
"Test from tracker - empty labels",
141+
"This is a test issue with no labels",
142+
&[],
143+
).await;
144+
145+
match result {
146+
Ok(issue) => println!("✅ Success! Created issue #{}: {}", issue.number, issue.title),
147+
Err(e) => println!("❌ Failed: {}", e),
148+
}
149+
}

0 commit comments

Comments
 (0)