Skip to content

Commit 14b17c0

Browse files
committed
fix: stabilize discord backup reporting
1 parent 9b42c6d commit 14b17c0

6 files changed

Lines changed: 124 additions & 22 deletions

File tree

.github/workflows/discord-backup-report.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,5 @@ jobs:
8181
fi
8282
git -C "$BACKUP_REPO" add README.md
8383
git -C "$BACKUP_REPO" commit -m "docs: update discord activity report"
84+
git -C "$BACKUP_REPO" pull --rebase --autostash origin main
8485
git -C "$BACKUP_REPO" push

internal/share/share.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,16 @@ func Push(ctx context.Context, opts Options) error {
146146
if strings.TrimSpace(branch) == "" {
147147
branch = "main"
148148
}
149+
out, err := output(ctx, opts.RepoPath, "git", "push", "-u", "origin", branch)
150+
if err == nil {
151+
return nil
152+
}
153+
if !isNonFastForwardPush(out) {
154+
return fmt.Errorf("git push -u origin %s: %w\n%s", branch, err, strings.TrimSpace(out))
155+
}
156+
if pullErr := run(ctx, opts.RepoPath, "git", "pull", "--rebase", "--autostash", "origin", branch); pullErr != nil {
157+
return fmt.Errorf("rebase before push retry: %w", pullErr)
158+
}
149159
return run(ctx, opts.RepoPath, "git", "push", "-u", "origin", branch)
150160
}
151161

@@ -515,3 +525,10 @@ func output(ctx context.Context, dir, name string, args ...string) (string, erro
515525
body, err := cmd.CombinedOutput()
516526
return string(body), err
517527
}
528+
529+
func isNonFastForwardPush(out string) bool {
530+
lower := strings.ToLower(out)
531+
return strings.Contains(lower, "non-fast-forward") ||
532+
strings.Contains(lower, "fetch first") ||
533+
strings.Contains(lower, "failed to push some refs")
534+
}

internal/share/share_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,48 @@ func TestPullAndPushWithBareRemote(t *testing.T) {
155155
require.FileExists(t, filepath.Join(subscriber, ManifestName))
156156
}
157157

158+
func TestPushRebasesRemoteReadmeUpdates(t *testing.T) {
159+
ctx := context.Background()
160+
src := seedStore(t, filepath.Join(t.TempDir(), "src.db"))
161+
defer func() { _ = src.Close() }()
162+
163+
dir := t.TempDir()
164+
remote := filepath.Join(dir, "remote.git")
165+
// #nosec G204 -- fixed git argv in test setup.
166+
require.NoError(t, exec.Command("git", "-C", dir, "init", "--bare", remote).Run())
167+
168+
publisher := filepath.Join(dir, "publisher")
169+
opts := Options{RepoPath: publisher, Remote: remote, Branch: "main"}
170+
_, err := Export(ctx, src, opts)
171+
require.NoError(t, err)
172+
configureGitUser(t, publisher)
173+
require.NoError(t, os.WriteFile(filepath.Join(publisher, "README.md"), []byte("report: first\n\nfield notes: old\n"), 0o600))
174+
committed, err := Commit(ctx, opts, "test: initial snapshot")
175+
require.NoError(t, err)
176+
require.True(t, committed)
177+
require.NoError(t, Push(ctx, opts))
178+
179+
reporter := filepath.Join(dir, "reporter")
180+
require.NoError(t, run(ctx, dir, "git", "clone", remote, reporter))
181+
configureGitUser(t, reporter)
182+
require.NoError(t, os.WriteFile(filepath.Join(reporter, "README.md"), []byte("report: first\n\nfield notes: fresh\n"), 0o600))
183+
require.NoError(t, run(ctx, reporter, "git", "commit", "-am", "docs: update field notes"))
184+
require.NoError(t, run(ctx, reporter, "git", "push", "-u", "origin", "main"))
185+
186+
require.NoError(t, os.WriteFile(filepath.Join(publisher, "README.md"), []byte("report: second\n\nfield notes: old\n"), 0o600))
187+
committed, err = Commit(ctx, opts, "test: update report")
188+
require.NoError(t, err)
189+
require.True(t, committed)
190+
require.NoError(t, Push(ctx, opts))
191+
192+
subscriber := filepath.Join(dir, "subscriber")
193+
require.NoError(t, Pull(ctx, Options{RepoPath: subscriber, Remote: remote, Branch: "main"}))
194+
body, err := os.ReadFile(filepath.Join(subscriber, "README.md"))
195+
require.NoError(t, err)
196+
require.Contains(t, string(body), "report: second")
197+
require.Contains(t, string(body), "field notes: fresh")
198+
}
199+
158200
func seedStore(t *testing.T, path string) *store.Store {
159201
t.Helper()
160202
ctx := context.Background()
@@ -202,6 +244,14 @@ func seedStore(t *testing.T, path string) *store.Store {
202244
return s
203245
}
204246

247+
func configureGitUser(t *testing.T, repo string) {
248+
t.Helper()
249+
// #nosec G204 -- fixed git argv in test setup.
250+
require.NoError(t, exec.Command("git", "-C", repo, "config", "user.name", "discrawl test").Run())
251+
// #nosec G204 -- fixed git argv in test setup.
252+
require.NoError(t, exec.Command("git", "-C", repo, "config", "user.email", "discrawl@example.com").Run())
253+
}
254+
205255
func tableEntry(t *testing.T, manifest Manifest, name string) TableManifest {
206256
t.Helper()
207257
for _, table := range manifest.Tables {

internal/store/query.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -517,7 +517,7 @@ func searchCandidateLimit(limit int) int {
517517

518518
func IsReadOnlySQL(query string) bool {
519519
switch leadingSQLKeyword(query) {
520-
case "select", "explain", "pragma":
520+
case "select", "with", "explain", "pragma":
521521
return true
522522
default:
523523
return false

internal/store/store_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,7 @@ func TestEventsSyncStateAndHelpers(t *testing.T) {
498498
require.Equal(t, "maintainers", normalizeChannelFilter(" maintainers "))
499499
require.True(t, IsReadOnlySQL("select 1"))
500500
require.True(t, IsReadOnlySQL("-- comment\nselect 1"))
501+
require.True(t, IsReadOnlySQL("with latest as (select 1 as one) select one from latest"))
501502
require.False(t, IsReadOnlySQL("delete from messages"))
502503
}
503504

scripts/discord-backup-field-notes.sh

Lines changed: 54 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -58,36 +58,64 @@ write_fallback_notes() {
5858
)
5959

6060
fallback_query "$recent_human_cte
61-
select channel, count(*) as matches
62-
from recent
63-
where $body_love_terms
61+
select topic, count(*) as matches
62+
from (
63+
select case
64+
when lower(body) like '%thank%' or lower(body) like '%helpful%' or lower(body) like '%useful%' then 'Helpful answers and practical fixes'
65+
when lower(body) like '%fast%' or lower(body) like '%speed%' or lower(body) like '%quick%' then 'Speed and responsiveness'
66+
when lower(body) like '%agent%' or lower(body) like '%workflow%' or lower(body) like '%automatic%' then 'Agent workflows and automation'
67+
when lower(body) like '%skill%' or lower(body) like '%tool%' or lower(body) like '%mcp%' then 'Skills, tools, and MCP integrations'
68+
else 'General positive feedback'
69+
end as topic
70+
from recent
71+
where $body_love_terms
72+
)
6473
group by 1
6574
order by matches desc
6675
limit 4;
6776
" | jq -r '
68-
if type == "array" then .[] else (.rows // [])[] | {channel: .[0], matches: .[1]} end |
69-
"- " + .channel + ": " + (.matches | tostring) + " positive mentions in the recent sample."
77+
if type == "array" then .[] else (.rows // [])[] | {topic: .[0], matches: .[1]} end |
78+
"- " + .topic + ": " + (.matches | tostring) + " positive mentions in the recent sample."
7079
' >"$TMP_DIR/fallback-love.md"
7180

7281
fallback_query "$recent_human_cte
73-
select channel, count(*) as matches
74-
from recent
75-
where $body_complaint_terms
82+
select topic, count(*) as matches
83+
from (
84+
select case
85+
when lower(body) like '%overload%' or lower(body) like '%fallback%' or lower(body) like '%provider%' or lower(body) like '%model%' then 'Provider reliability and model fallback'
86+
when lower(body) like '%token%' or lower(body) like '%secret%' or lower(body) like '%auth%' or lower(body) like '%config%' or lower(body) like '%install%' then 'Setup, auth, and configuration'
87+
when lower(body) like '%github%' or lower(body) like '%repo%' or lower(body) like '%pr%' or lower(body) like '%issue%' then 'GitHub and repository workflow'
88+
when lower(body) like '%skill%' or lower(body) like '%tool%' or lower(body) like '%mcp%' then 'Skills, tools, and runtime bridges'
89+
when lower(body) like '%encoding%' or lower(body) like '%character%' or lower(body) like '%message%' then 'Message quality, editing, and encoding'
90+
else 'General bugs, failures, and confusing behavior'
91+
end as topic
92+
from recent
93+
where $body_complaint_terms
94+
)
7695
group by 1
7796
order by matches desc
7897
limit 4;
7998
" | jq -r '
80-
if type == "array" then .[] else (.rows // [])[] | {channel: .[0], matches: .[1]} end |
81-
"- " + .channel + ": " + (.matches | tostring) + " complaint-flavored mentions in the recent sample; compare this with the issue/PR cluster below."
99+
if type == "array" then .[] else (.rows // [])[] | {topic: .[0], matches: .[1]} end |
100+
"- " + .topic + ": " + (.matches | tostring) + " complaint-flavored mentions in the recent sample; compare with the issue/PR cluster below."
82101
' >"$TMP_DIR/fallback-complaints.md"
83102

84103
if command -v gh >/dev/null 2>&1; then
85-
gh search issues "repo:$GH_REPO updated:>=$github_since" \
104+
gh search issues --repo "$GH_REPO" --updated ">=$github_since" \
86105
--json number,title,state,updatedAt,url,labels \
87106
--limit 8 | jq -r '.[0:3][]? | "- Issue #" + (.number | tostring) + " (" + .state + "): [" + .title + "](" + .url + ")"' >"$TMP_DIR/fallback-issues.md" || :
88-
gh search prs "repo:$GH_REPO updated:>=$github_since" \
107+
gh search prs --repo "$GH_REPO" --updated ">=$github_since" \
89108
--json number,title,state,updatedAt,url,labels \
90-
--limit 8 | jq -r 'map(select(.state == "open"))[0] // .[0] // empty | "- PR #" + (.number | tostring) + ": [" + .title + "](" + .url + ") looks like the highest-leverage recent PR because it is active in the same window as the complaint cluster."' >"$TMP_DIR/fallback-pr.md" || :
109+
--limit 25 | jq -r '
110+
def score($title):
111+
[ "fix", "bug", "fail", "error", "provider", "fallback", "auth", "config", "skill", "tool", "github", "encoding" ]
112+
| map(if $title | contains(.) then 1 else 0 end)
113+
| add;
114+
map(. + {score: score(.title | ascii_downcase)})
115+
| map(select(.state == "open")) as $open
116+
| ($open // .) | sort_by(.score, .updatedAt) | reverse | .[0] // empty
117+
| "- PR #" + (.number | tostring) + ": [" + .title + "](" + .url + ") is the best recent watch candidate from title/recency signals."
118+
' >"$TMP_DIR/fallback-pr.md" || :
91119
fi
92120

93121
if [ ! -s "$TMP_DIR/fallback-love.md" ]; then
@@ -99,6 +127,7 @@ limit 4;
99127
if [ -s "$TMP_DIR/fallback-issues.md" ]; then
100128
{
101129
printf '\n'
130+
printf '%s\n' "Related GitHub issue cluster:"
102131
cat "$TMP_DIR/fallback-issues.md"
103132
} >>"$TMP_DIR/fallback-complaints.md"
104133
fi
@@ -142,12 +171,16 @@ run_openclaw_agent() {
142171

143172
extract_openclaw_text() {
144173
jq -r '
145-
.payloads[0].text //
146-
.finalAssistantVisibleText //
147-
.finalAssistantRawText //
148-
.result.finalAssistantVisibleText //
149-
.result.finalAssistantRawText //
150-
empty
174+
[
175+
.payloads[]?.text?,
176+
.finalAssistantVisibleText?,
177+
.finalAssistantRawText?,
178+
.result.finalAssistantVisibleText?,
179+
.result.finalAssistantRawText?,
180+
(.. | objects | .finalAssistantVisibleText?),
181+
(.. | objects | .finalAssistantRawText?)
182+
]
183+
| map(select(type == "string" and length > 0))[0] // empty
151184
' "$TMP_DIR/openclaw-result.json"
152185
}
153186

@@ -258,15 +291,15 @@ limit 12;
258291
{
259292
printf "\n## GitHub Pull Requests Updated This Month\n\n"
260293
if command -v gh >/dev/null 2>&1; then
261-
gh search prs "repo:$GH_REPO updated:>=$github_since" \
294+
gh search prs --repo "$GH_REPO" --updated ">=$github_since" \
262295
--json number,title,state,updatedAt,url,labels \
263296
--limit 25 | jq -c . || printf "[]\n"
264297
else
265298
printf "gh unavailable\n"
266299
fi
267300
printf "\n## GitHub Issues Updated This Month\n\n"
268301
if command -v gh >/dev/null 2>&1; then
269-
gh search issues "repo:$GH_REPO updated:>=$github_since" \
302+
gh search issues --repo "$GH_REPO" --updated ">=$github_since" \
270303
--json number,title,state,updatedAt,url,labels \
271304
--limit 25 | jq -c . || printf "[]\n"
272305
else

0 commit comments

Comments
 (0)