Skip to content

fix: large archive listings fail at SQLite parameter limit - #238

Merged
steipete merged 1 commit into
mainfrom
fix/large-message-listings
Sep 15, 2026
Merged

steipete merged 1 commit into
mainfrom
fix/large-message-listings

Conversation

@steipete

@steipete steipete commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

Large archive reads fail with SQL logic error: too many SQL variables once a message, reply, or mention lookup contains more than 32,766 IDs. This breaks messages --all, large --last slices, and large TUI loads even when the initial message query succeeds.

User Impact

Large local listings return every requested row with mention display names and reply context intact. Existing ordering, limits, and read-only access remain unchanged.

Why This Change Was Made

Bind each hydration ID set as one JSON array and expand it with SQLite's existing JSON support. This removes the parameter-count failure in all four hydration queries without truncating results or changing database schemas.

Evidence

  • A synthetic 32,767-message archive makes the original built CLI exit 1 with too many SQL variables; the patched CLI returns all 32,767 rows through messages --all, messages --last 32767, and tui --limit 32767.
  • Four regression subtests fail before the fix and pass afterward, covering uncapped listings, reply roots, user names, and channel names, including ordering and labels.
  • Full local race suite passed with 86.0% coverage (85% floor); formatting, dependency checks, static analyzers, vulnerability checks, CLI smoke, and six-platform snapshot builds passed via make check.
  • Documentation tests and site build passed.
  • Independent Codex autoreview returned scoped-clean at P0–P2.

Proof uses synthetic local data; no Discord credentials or private archive content are required.

Built CLI output from the synthetic archive

Built discrawl-before from base 0d365ac and discrawl-after from the reviewed patch. Each command used --config <synthetic-config> --json; the fixture has 32,767 matching messages plus one reply root outside the selected channel. The JSON below records actual exit codes and parsed output, with assertions checking counts, ordering, labels, and root presence.

[
  {
    "binary": "discrawl-before",
    "args": [
      "messages",
      "--channel",
      "100000000000000001",
      "--all"
    ],
    "exit_code": 1,
    "stderr": "SQL logic error: too many SQL variables (1)"
  },
  {
    "binary": "discrawl-after",
    "args": [
      "messages",
      "--channel",
      "100000000000000001",
      "--all"
    ],
    "exit_code": 0,
    "rows": 32767,
    "first": "m00001",
    "last": "m32767",
    "display_content": "ping @Synthetic Reader #synthetic-channel @Readers",
    "reply_to_message_id": "root"
  },
  {
    "binary": "discrawl-after",
    "args": [
      "messages",
      "--channel",
      "100000000000000001",
      "--last",
      "32767"
    ],
    "exit_code": 0,
    "rows": 32767,
    "first": "m00001",
    "last": "m32767",
    "display_content": "ping @Synthetic Reader #synthetic-channel @Readers",
    "reply_to_message_id": "root"
  },
  {
    "binary": "discrawl-after",
    "args": [
      "tui",
      "--channel",
      "100000000000000001",
      "--limit",
      "32767"
    ],
    "exit_code": 0,
    "rows": 32768,
    "resolved_mentions": true,
    "reply_root_present": true
  }
]

The regression command GOWORK=off go test ./internal/store -run '^TestMessageHydrationBeyondSQLiteVariableLimit$' -count=1 -v fails in all four subtests on the base with too many SQL variables; after the fix it reports:

=== RUN   TestMessageHydrationBeyondSQLiteVariableLimit
=== PAUSE TestMessageHydrationBeyondSQLiteVariableLimit
=== CONT  TestMessageHydrationBeyondSQLiteVariableLimit
=== RUN   TestMessageHydrationBeyondSQLiteVariableLimit/uncapped_listing
=== RUN   TestMessageHydrationBeyondSQLiteVariableLimit/reply_roots
=== RUN   TestMessageHydrationBeyondSQLiteVariableLimit/user_names
=== RUN   TestMessageHydrationBeyondSQLiteVariableLimit/channel_names
--- PASS: TestMessageHydrationBeyondSQLiteVariableLimit (3.82s)
    --- PASS: TestMessageHydrationBeyondSQLiteVariableLimit/uncapped_listing (0.16s)
    --- PASS: TestMessageHydrationBeyondSQLiteVariableLimit/reply_roots (0.21s)
    --- PASS: TestMessageHydrationBeyondSQLiteVariableLimit/user_names (0.05s)
    --- PASS: TestMessageHydrationBeyondSQLiteVariableLimit/channel_names (0.06s)
PASS
ok  	github.com/openclaw/discrawl/internal/store	4.201s

@clawsweeper

clawsweeper Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper picked this up.

Pull request received. I will update this pull request when review starts.

ClawSweeper review complete

ClawSweeper finished reviewing this revision. The review result is being finalized.

View the workflow run.

@clawsweeper clawsweeper Bot added P2 Normal priority bug or improvement with limited blast radius. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Sep 15, 2026
@clawsweeper

clawsweeper Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge. Reviewed September 15, 2026, 3:17 AM ET / 07:17 UTC.

ClawSweeper review

What this changes

The PR passes reply and mention lookup IDs as JSON arrays so large archive listings retain their rows and display names without exceeding SQLite’s parameter limit.

Merge readiness

Blocked before merge - 2 items remain

This remains necessary: current main and v0.15.1 still expand hydration IDs into individual SQL parameters. The focused patch has no identified correctness defect.

Priority: P2
Reviewed head: 8ec8e023b167da80aaae7b361895fe229a3f897e

Review scores

Measure Result What it means
Overall readiness 🦐 gold shrimp (3/6) The repair is focused and supported by useful regression coverage; runtime verification remains a reported result without inspectable evidence.
Proof confidence 🦐 gold shrimp (3/6) Needs stronger real behavior proof before merge: The body reports the relevant built-CLI exercise against a synthetic SQLite archive, including before/after row counts, but provides no inspectable terminal output or artifact showing the changed hydration path succeeding. Attach the reported run’s transcript or terminal screenshot with row counts and resolved labels; synthetic data is appropriate. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Patch quality 🐚 platinum hermit (4/6) No actionable review findings were identified.

Verification

Check Result Evidence
Real behavior Needs proof Needs stronger real behavior proof before merge: The body reports the relevant built-CLI exercise against a synthetic SQLite archive, including before/after row counts, but provides no inspectable terminal output or artifact showing the changed hydration path succeeding. Attach the reported run’s transcript or terminal screenshot with row counts and resolved labels; synthetic data is appropriate. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Evidence reviewed 7 items Current main still contains the failure mechanism: All four hydration queries construct one placeholder per requested ID; listing results feed directly into mention hydration without a separate bound on their number.
Latest release retains the same queries: Inspection of the supplied v0.15.1 release revision found individual placeholders in reply-root, mention-event, member-name, and channel-name hydration.
Bounded query repair: The complete production file uses JSON-encoded string arrays through a bound parameter in four SELECT queries. Filters, result merging, ordering, and name replacement remain intact; no schema or write path changes.
Findings None None.
Security None None.

How this fits together

Discrawl reads archived Discord messages from local SQLite storage for CLI listings and terminal browsing. Its hydration queries add reply context and readable mention names before displaying those messages.

flowchart TD
 A[CLI listing or terminal browser] --> B[Select archived messages]
 C[Local SQLite archive] --> B
 B --> D[Collect reply and mention IDs]
 D --> E[Bind IDs as JSON arrays]
 E --> F[Look up context and display names]
 C --> F
 F --> G[Display requested messages]
Loading

Before merge

  • Add real behavior proof - Needs stronger real behavior proof before merge: The body reports the relevant built-CLI exercise against a synthetic SQLite archive, including before/after row counts, but provides no inspectable terminal output or artifact showing the changed hydration path succeeding. Attach the reported run’s transcript or terminal screenshot with row counts and resolved labels; synthetic data is appropriate. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
  • Complete next step (P2) - Add inspectable evidence from the reported CLI run before merge. Terminal screenshots or recordings are welcome; copied output and logs also count. Redact credentials, private endpoints, and archive details. Updating the PR body should trigger review automatically; otherwise ask a maintainer to comment @clawsweeper re-review.
Agent review details

Security

None.

Review metrics

Metric Value Why it matters
Production and test LOC production +20/-17 (net +3); tests +80 The small production increase replaces four unbounded parameter lists, with coverage for each lookup.

Technical review

Best possible solution:

Keep hydration as parameterized, read-only queries that preserve complete ID sets and existing ordering without adding limits or migrations.

Do we have a high-confidence way to reproduce the issue?

Yes, from source: a listing of 32,767 messages creates that many bound parameters in current-main mention hydration. This review did not execute the reproduction.

Is this the best way to solve the issue?

Yes. A single bound JSON array removes the parameter-count failure across all four lookups while preserving string IDs, query predicates, and existing display behavior.

AGENTS.md: not found in the target repository.

Codex review notes: model internal, reasoning medium; reviewed against 0d365ac80195.

Labels

Label changes:

  • add P2: This repairs a concrete archive-read failure affecting unusually large listings without changing ordinary workflows.
  • add rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦐 gold shrimp and patch quality is 🐚 platinum hermit.
  • add status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs stronger real behavior proof before merge: The body reports the relevant built-CLI exercise against a synthetic SQLite archive, including before/after row counts, but provides no inspectable terminal output or artifact showing the changed hydration path succeeding. Attach the reported run’s transcript or terminal screenshot with row counts and resolved labels; synthetic data is appropriate. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Label justifications:

  • P2: This repairs a concrete archive-read failure affecting unusually large listings without changing ordinary workflows.
  • rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦐 gold shrimp and patch quality is 🐚 platinum hermit.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs stronger real behavior proof before merge: The body reports the relevant built-CLI exercise against a synthetic SQLite archive, including before/after row counts, but provides no inspectable terminal output or artifact showing the changed hydration path succeeding. Attach the reported run’s transcript or terminal screenshot with row counts and resolved labels; synthetic data is appropriate. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Evidence

What I checked:

  • Current main still contains the failure mechanism: All four hydration queries construct one placeholder per requested ID; listing results feed directly into mention hydration without a separate bound on their number. (internal/store/messages.go:365, 0d365ac80195)
  • Latest release retains the same queries: Inspection of the supplied v0.15.1 release revision found individual placeholders in reply-root, mention-event, member-name, and channel-name hydration. (internal/store/messages.go:365, ab58a5307dda)
  • Bounded query repair: The complete production file uses JSON-encoded string arrays through a bound parameter in four SELECT queries. Filters, result merging, ordering, and name replacement remain intact; no schema or write path changes. (internal/store/messages.go:514, 8ec8e023b167)
  • Regression coverage: The new test creates 32,767 messages and checks uncapped listing, reply roots, user names, and channel names, including first/last rows and resolved labels. Tests were inspected, not executed during this read-only review. (internal/store/messages_large_test.go:12, 8ec8e023b167)
  • Reported runtime verification lacks inspectable output: The complete supplied body reports an original CLI failure and patched results of 32,767 rows through messages --all, messages --last 32767, and tui --limit 32767. The live body matches that snapshot, but supplies no transcript, runtime output, or artifact link. Review comments and reviews are empty; the supplied discussion contains only filtered bot comments. The reported CLI exercise is relevant and is not merely a unit-test claim. (8ec8e023b167)
  • Feature-history routing: Main-branch history contains repeated reply-context and mention-rendering work by Vincent Koc. GitHub commit metadata identifies vincentkoc, and the patch at this commit adds the inline mention collection used by the changed lookups. Broader local blame traversal encountered unavailable historical objects, so no broader introduction claim is made. (internal/store/messages.go:416, 058eb0699e74)

Likely related people:

  • unknown: The claimed source-line change could not be verified from bounded local history. (role: source history unknown; confidence: low)

Rank-up moves

Optional improvements that raise the rating; they are not merge blockers.

  • Attach terminal output or a terminal screenshot from the reported patched CLI run showing all 32,767 rows and retained mention labels or reply context.

Rating scale

Score Internal tier Crab rank Meaning
6/6 S 🦀 challenger crab Exceptional readiness
5/6 A 🦞 diamond lobster Very strong readiness
4/6 B 🐚 platinum hermit Good normal PR; ordinary maintainer review
3/6 C 🦐 gold shrimp Useful, but confidence is limited
2/6 D 🦪 silver shellfish Proof or implementation needs work
1/6 F 🧂 unranked krab Not merge-ready
N/A NA 🌊 off-meta tidepool Rating does not apply

Overall follows the weaker of proof and patch quality.
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

Workflow

  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@steipete
steipete merged commit b1ea76e into main Sep 15, 2026
26 checks passed
@steipete

Copy link
Copy Markdown
Contributor Author

Merged as b1ea76e after all checks on head 8ec8e02 settled: 19 passed, two expected skips, no failures.

Validation: make check passed, including race tests, 86.0% coverage (85% floor), formatting, dependency checks, static analysis, vulnerability checks, CLI smoke, and six-platform unpublished snapshot builds. Documentation tests/site build and independent P0–P2 autoreview passed. Main CI run.

The built baseline failed on the synthetic 32,767-message archive. The fixed binary returned every row through messages --all and messages --last 32767, preserved resolved user/channel/role names, and loaded 32,768 TUI rows when including the reply root. Actual terminal results and the four passing regression subtests are in the PR body. All behavior proof used synthetic local data; no live Discord account was needed.

@steipete
steipete deleted the fix/large-message-listings branch September 15, 2026 07:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P2 Normal priority bug or improvement with limited blast radius. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant