Skip to content

Add generated ABI contract for schema and eBPF#3

Merged
Agent-Hellboy merged 10 commits into
mainfrom
abi-contract-codegen
Jul 12, 2026
Merged

Add generated ABI contract for schema and eBPF#3
Agent-Hellboy merged 10 commits into
mainfrom
abi-contract-codegen

Conversation

@Agent-Hellboy

@Agent-Hellboy Agent-Hellboy commented Jul 11, 2026

Copy link
Copy Markdown
Owner

Summary

  • add an internal/abi source-of-truth generator for tracepoints, schema docs, storage DDL, and the kernel event header
  • switch the Linux collector to bpf2go-generated bindings and add drift checks for the compiled object/schema outputs
  • update docs and CI to reflect and enforce the generate/ebpf workflow

Testing

  • go test ./internal/abi ./internal/storage ./internal/ebpf ./internal/agent ./internal/cli ./internal/daemon ./internal/query ./internal/diff

Summary by CodeRabbit

  • New Features
    • Added a centralized ABI contract covering eBPF capture, tracepoints, and the persisted events schema, plus generation/verification to keep artifacts consistent.
    • Added runtime recorder commands (start/stop/status/timeline/watch/benchmark/prune/daemon).
    • Improved demo/benchmark seeding so synthetic events are easier to inspect via timeline/diff/query.
  • Documentation
    • Added comprehensive ABI references (capture contract, schema ABI, architecture) and updated querying/help text and platform rebuild guidance.
  • Bug Fixes
    • Improved consistency of schema-version usage across capture, storage, querying, diffing, and agent-guided SQL; added stronger read-only SQL safeguards.
  • CI / Chores
    • CI now fails when generated ABI/eBPF/schema artifacts are out of sync with the ABI source.

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change centralizes tracepoint, SQLite schema, and kernel event definitions in internal/abi/abi.yaml, generates Go/C/eBPF artifacts, adds Linux capture and SQLite runtime paths, replaces CLI dispatch with Cobra, and documents generation workflows with CI drift checks.

Changes

ABI centralization and generated artifacts

Layer / File(s) Summary
ABI contracts and generated schema artifacts
internal/abi/*, internal/storage/schema_gen.go, internal/storage/schema_gen_test.go, .github/workflows/ci.yml, AGENTS.md, go.mod
Defines the ABI source of truth, generates schema/version constants, tracepoint declarations, kernel event headers, and storage schema artifacts, with consistency and generated-output checks.
Generated eBPF capture pipeline
internal/ebpf/*, internal/collector/*, internal/daemon/*
Uses the generated kernel event layout to build and embed the collector, attaches ABI-defined tracepoints, decodes generated event records, filters paths, batches events, and validates embedded programs against the tracepoint table.
Storage ownership and event consumers
internal/storage/*, internal/agent/agent.go, internal/*/engine_test.go
Adds SQLite opening, read-only querying, event insertion, pruning, demo generation, and centralized ABI schema-version usage across prompts, storage, daemon ingestion, and fixtures.
CLI command and runtime orchestration
internal/cli/*
Adds Cobra command routing, global flag parsing, query/diff and SQL flows, daemon lifecycle commands, timeline/watch/benchmark/prune operations, signal handling, filtering, and formatted output.
Generation workflow and repository documentation
Makefile, README.md, docs/*, AGENTS.md, .github/workflows/ci.yml
Adds generation targets, CI generated-file drift checks, ABI and eBPF rebuild guidance, ABI documentation, architecture updates, and recording/query references.

Estimated code review effort: 5 (Critical) | ~90 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main addition of a generated ABI contract covering schema and eBPF.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch abi-contract-codegen

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
internal/abi/abi.yaml (1)

360-386: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Persist bytes in metadata. convertKernelEvent already serializes syscall_nr, fd, and aux, but bytes is dropped before storage. Add it to Metadata (or Raw) so the I/O byte count isn’t lost.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/abi/abi.yaml` around lines 360 - 386, Update the metadata/raw
representation used by convertKernelEvent to include the kernel_event bytes
field, and serialize its value alongside syscall_nr, fd, and aux before storage.
Preserve the existing byte count without changing handling of the other event
fields.
internal/daemon/service.go (1)

108-127: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Drain path missing SchemaVersion assignment.

The normal ingest path (line 127) sets ev.SchemaVersion = abi.SchemaVersion, but the shutdown drain path (lines 108–118) appends events to the batch without this assignment. Events arriving during shutdown will have SchemaVersion = 0 instead of 1, which could break downstream consumers that check schema compatibility.

🐛 Proposed fix: set SchemaVersion in the drain path
 				case ev, ok := <-ingest:
 					if !ok {
 						drained = true
 					} else {
+						ev.SchemaVersion = abi.SchemaVersion
 						batch = append(batch, ev)
 					}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/daemon/service.go` around lines 108 - 127, Update the shutdown drain
loop in the ingest handling flow to assign abi.SchemaVersion to each event
before appending it to batch, matching the assignment in the normal case ev
path. Ensure events drained during shutdown carry the same schema version as
regular ingested events.
🧹 Nitpick comments (1)
internal/storage/schema_gen_test.go (1)

20-74: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Good schema consistency test.

The schemaDocColumns parser correctly extracts column names from abi.SchemaDoc by filtering lines prefixed with two spaces and taking the first field. The Indexes: line and blank lines are naturally excluded since they don't start with " ".

The errcheck lint findings on store.Close() (line 25) and rows.Close() (line 31) are common in Go test code. If the project enforces errcheck in CI, consider wrapping with t.Cleanup(func() { _ = store.Close() }) as done in internal/diff/engine_test.go:19.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/storage/schema_gen_test.go` around lines 20 - 74, Update
TestSchemaMatchesGenerated cleanup to handle Close errors through t.Cleanup
callbacks that explicitly discard the results of store.Close and rows.Close,
matching the project’s established test pattern and satisfying errcheck.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/abi/capture.md`:
- Around line 29-36: Update the “Source of truth” section in capture.md to
identify only internal/abi/abi.yaml as the ABI source input. Move
collector_bpfel.o, kernel_event.gen.h, and tracepoints_gen.go into a
generated-artifacts list, while retaining collector.bpf.c and real_linux.go
under the appropriate source inputs category.

---

Outside diff comments:
In `@internal/abi/abi.yaml`:
- Around line 360-386: Update the metadata/raw representation used by
convertKernelEvent to include the kernel_event bytes field, and serialize its
value alongside syscall_nr, fd, and aux before storage. Preserve the existing
byte count without changing handling of the other event fields.

In `@internal/daemon/service.go`:
- Around line 108-127: Update the shutdown drain loop in the ingest handling
flow to assign abi.SchemaVersion to each event before appending it to batch,
matching the assignment in the normal case ev path. Ensure events drained during
shutdown carry the same schema version as regular ingested events.

---

Nitpick comments:
In `@internal/storage/schema_gen_test.go`:
- Around line 20-74: Update TestSchemaMatchesGenerated cleanup to handle Close
errors through t.Cleanup callbacks that explicitly discard the results of
store.Close and rows.Close, matching the project’s established test pattern and
satisfying errcheck.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8bdfe172-29d1-4d30-b367-8c40a94cd939

📥 Commits

Reviewing files that changed from the base of the PR and between 17e6aef and 9b12cc3.

⛔ Files ignored due to path filters (3)
  • go.sum is excluded by !**/*.sum
  • internal/abi/gen/main.go is excluded by !**/gen/**
  • internal/ebpf/collector_bpfel.o is excluded by !**/*.o
📒 Files selected for processing (31)
  • .github/workflows/ci.yml
  • AGENTS.md
  • Makefile
  • README.md
  • docs/README.md
  • docs/abi/README.md
  • docs/abi/capture.md
  • docs/abi/schema.md
  • docs/architecture.md
  • docs/querying.md
  • docs/recording.md
  • go.mod
  • internal/abi/abi.yaml
  • internal/abi/gen.go
  • internal/abi/kernel_event.gen.h
  • internal/abi/schema_gen.go
  • internal/abi/tracepoints_gen.go
  • internal/agent/agent.go
  • internal/cli/cli.go
  • internal/daemon/service.go
  • internal/diff/engine_test.go
  • internal/ebpf/collector.bpf.c
  • internal/ebpf/collector_bpfel.go
  • internal/ebpf/collector_contract_test.go
  • internal/ebpf/gen.go
  • internal/ebpf/real_linux.go
  • internal/query/engine_test.go
  • internal/storage/schema_gen.go
  • internal/storage/schema_gen_test.go
  • internal/storage/store.go
  • internal/storage/types.go
💤 Files with no reviewable changes (1)
  • internal/storage/types.go

Comment thread docs/abi/capture.md Outdated
parseGlobalFlags now accepts single-dash flags and stops scanning at
the first unrecognized token instead of scanning the whole arg list,
so flag-shaped words inside free-text query/SQL input are no longer
mistaken for global flags. Global flags are now stripped from os.Args
before cobra ever runs, since pflag doesn't understand "-longname" the
way the old stdlib parser did.

Also: stop advertising --version as an inherited subcommand flag (it
never worked there), reject non-positive --interval in `watch` instead
of panicking, drop the dead printRootHelp, point AGENTS.md/capture.md
at the real eBPF loader files instead of the deleted real_linux.go/
real_stub.go, and cross-compile for darwin in CI now that macOS
runners are gone.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (5)
internal/ebpf/collector_linux.go (1)

93-96: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Silently dropped decode errors reduce observability.

If the eBPF struct layout drifts from the Go decoder, every event fails to decode with no visible indication. Consider adding a counter or periodic stderr log for decode failures to aid production debugging.

♻️ Suggested improvement: add a decode-error counter
 	var dropped uint64
+	var decodeErrs uint64
 
 	go func() {
 		<-ctx.Done()
 		_ = reader.Close()
 	}()
 
 	for {
 		record, err := reader.Read()
 		if err != nil {
 			if errors.Is(err, perf.ErrClosed) {
+				if decodeErrs > 0 {
+					fmt.Fprintf(os.Stderr, "ltm: ebpf collector failed to decode %d events\n", decodeErrs)
+				}
 				return nil
 			}
 			return err
 		}
 		if record.LostSamples != 0 {
 			dropped += record.LostSamples
 			continue
 		}
 		ke, err := decodeKernelEvent(record.RawSample)
 		if err != nil {
+			decodeErrs++
 			continue
 		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/ebpf/collector_linux.go` around lines 93 - 96, Instrument decode
failures in the event-processing loop around decodeKernelEvent by incrementing a
dedicated decode-error counter, and expose or periodically report that counter
through the collector’s existing observability mechanism. Preserve the current
continue behavior so malformed events remain skipped while failures become
visible in production.
internal/storage/write.go (1)

29-35: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Satisfy errcheck on the deferred Rollback/Close.

golangci-lint reports these as errors. defer tx.Rollback() and defer stmt.Close() are idiomatic, but to keep the lint gate green, ignore the returns explicitly.

♻️ Explicitly discard the deferred error returns
-	defer tx.Rollback()
+	defer func() { _ = tx.Rollback() }()
-	defer stmt.Close()
+	defer func() { _ = stmt.Close() }()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/storage/write.go` around lines 29 - 35, Update the deferred cleanup
calls in the insert flow around tx and stmt to explicitly discard their error
returns, satisfying errcheck while preserving the existing rollback and close
behavior.

Source: Linters/SAST tools

internal/cli/commands_runtime.go (1)

38-44: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

noctx here is a deliberate design choice — suppress it to keep CI green.

The recorder is intentionally detached (Setsid) so it outlives the launching process; switching to exec.CommandContext would tie its lifetime to a cancelable context and kill it prematurely. Add a scoped //nolint with rationale so the lint error doesn't block the pipeline.

♻️ Document the intentional detachment
-	cmd := exec.Command(exe, daemonArgs(cfg)...)
+	// The daemon is detached (Setsid) to outlive this process, so it must not be
+	// bound to a cancelable context.
+	cmd := exec.Command(exe, daemonArgs(cfg)...) //nolint:noctx // detached long-lived daemon
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/commands_runtime.go` around lines 38 - 44, Add a scoped noctx
lint suppression at the exec.Command call in the runtime command launch flow,
with a brief rationale that Setsid intentionally detaches the recorder and it
must outlive the launching process; preserve exec.Command rather than switching
to a context-bound variant.

Source: Linters/SAST tools

internal/collector/filter.go (1)

17-19: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Avoid appending onto the shared defaultIgnorePaths slice.

append(defaultIgnorePaths, extra...) reuses the package-level backing array whenever it has spare capacity, which can silently corrupt the defaults across callers. It's safe today only because the literal's len == cap; copying into a fresh slice removes that latent aliasing hazard.

♻️ Build a fresh slice
 func newIgnoreRules(extra []string) []string {
-	return normalizeIgnoreRules(append(defaultIgnorePaths, extra...))
+	combined := make([]string, 0, len(defaultIgnorePaths)+len(extra))
+	combined = append(combined, defaultIgnorePaths...)
+	combined = append(combined, extra...)
+	return normalizeIgnoreRules(combined)
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/collector/filter.go` around lines 17 - 19, Update newIgnoreRules to
build a fresh slice containing defaultIgnorePaths before appending extra, rather
than appending directly to the shared package-level slice. Preserve the existing
normalizeIgnoreRules behavior and returned rule ordering while preventing
backing-array aliasing across callers.
internal/daemon/flush.go (1)

56-56: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Remove the redundant SchemaVersion write
InsertEvents never stores SchemaVersion, and scanEvent restores it from abi.SchemaVersion on read, so this assignment only touches the in-memory batch and can be dropped.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/daemon/flush.go` at line 56, Remove the redundant ev.SchemaVersion
assignment from InsertEvents, leaving scanEvent’s restoration from
abi.SchemaVersion unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/storage/maintenance.go`:
- Around line 23-28: Update the prune flow around the VACUUM execution in the
maintenance method to avoid running a full VACUUM unconditionally after every
prune. Prefer the supported incremental_vacuum strategy when the database is
configured with auto_vacuum=INCREMENTAL, or gate full VACUUM behind an explicit
opt-in or periodic maintenance condition while preserving the zero-row early
return and prune result handling.

In `@internal/storage/open.go`:
- Around line 38-43: Update the schema initialization loop in Open to call
db.ExecContext with context.Background() instead of db.Exec, preserving the
existing error cleanup and wrapping behavior so initialization remains
cancellable-compatible and satisfies the noctx linter.

In `@internal/storage/query.go`:
- Around line 65-92: Remove the one-off EventsByPID, EventsByPath, and
EventsBetween helpers from the Store API, and extend Filter to support their
query semantics. Reuse Query with Filter{PIDs: []int{pid}} and Filter{From:
from, To: to}; add an exact-path filter field for EventsByPath that preserves
path = ? OR old_path = ? behavior, distinct from PathLike. Keep EventsAfterID
and LatestEventID unchanged.
- Around line 239-268: Update execReadOnlySQL to wrap its context with the same
two-minute timeout used by the agent path before calling Store.RawSQL, ensuring
the CLI cannot run arbitrary read-only SQL indefinitely while preserving
RawSQL’s caller-provided context behavior.

---

Nitpick comments:
In `@internal/cli/commands_runtime.go`:
- Around line 38-44: Add a scoped noctx lint suppression at the exec.Command
call in the runtime command launch flow, with a brief rationale that Setsid
intentionally detaches the recorder and it must outlive the launching process;
preserve exec.Command rather than switching to a context-bound variant.

In `@internal/collector/filter.go`:
- Around line 17-19: Update newIgnoreRules to build a fresh slice containing
defaultIgnorePaths before appending extra, rather than appending directly to the
shared package-level slice. Preserve the existing normalizeIgnoreRules behavior
and returned rule ordering while preventing backing-array aliasing across
callers.

In `@internal/daemon/flush.go`:
- Line 56: Remove the redundant ev.SchemaVersion assignment from InsertEvents,
leaving scanEvent’s restoration from abi.SchemaVersion unchanged.

In `@internal/ebpf/collector_linux.go`:
- Around line 93-96: Instrument decode failures in the event-processing loop
around decodeKernelEvent by incrementing a dedicated decode-error counter, and
expose or periodically report that counter through the collector’s existing
observability mechanism. Preserve the current continue behavior so malformed
events remain skipped while failures become visible in production.

In `@internal/storage/write.go`:
- Around line 29-35: Update the deferred cleanup calls in the insert flow around
tx and stmt to explicitly discard their error returns, satisfying errcheck while
preserving the existing rollback and close behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d4f9a5db-634b-44e2-ab3f-9122f840f92e

📥 Commits

Reviewing files that changed from the base of the PR and between 9b12cc3 and 16973cf.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (35)
  • .github/workflows/ci.yml
  • AGENTS.md
  • README.md
  • docs/README.md
  • docs/abi/README.md
  • docs/abi/capture.md
  • docs/abi/schema.md
  • docs/architecture.md
  • docs/querying.md
  • docs/recording.md
  • go.mod
  • internal/cli/cli.go
  • internal/cli/cli_test.go
  • internal/cli/commands_query.go
  • internal/cli/commands_runtime.go
  • internal/cli/flags.go
  • internal/cli/output.go
  • internal/collector/collector.go
  • internal/collector/filter.go
  • internal/daemon/flush.go
  • internal/daemon/service.go
  • internal/ebpf/attach_linux.go
  • internal/ebpf/collector_linux.go
  • internal/ebpf/collector_stub.go
  • internal/ebpf/decode_linux.go
  • internal/ebpf/proc_linux.go
  • internal/ebpf/real_linux.go
  • internal/ebpf/real_stub.go
  • internal/ebpf/source.go
  • internal/storage/demo.go
  • internal/storage/maintenance.go
  • internal/storage/open.go
  • internal/storage/query.go
  • internal/storage/store.go
  • internal/storage/write.go
💤 Files with no reviewable changes (3)
  • internal/ebpf/real_stub.go
  • internal/ebpf/real_linux.go
  • internal/storage/store.go
✅ Files skipped from review due to trivial changes (8)
  • internal/storage/demo.go
  • docs/README.md
  • internal/ebpf/source.go
  • docs/abi/README.md
  • docs/recording.md
  • docs/architecture.md
  • README.md
  • docs/querying.md
🚧 Files skipped from review as they are similar to previous changes (5)
  • .github/workflows/ci.yml
  • docs/abi/schema.md
  • AGENTS.md
  • internal/daemon/service.go
  • docs/abi/capture.md

Comment thread internal/storage/maintenance.go Outdated
Comment thread internal/storage/open.go
Comment thread internal/storage/query.go Outdated
Comment thread internal/storage/query.go Outdated
Comment on lines +239 to +268
// RawSQL executes an arbitrary read-only query and returns column names plus
// rows as generic values, for `ltm sql`. Callers must use a read-only Store
// (query_only=ON) so writes fail at the SQLite layer.
func (s *Store) RawSQL(ctx context.Context, query string) ([]string, [][]any, error) {
if !s.readOnly {
return nil, nil, errors.New("raw SQL requires read-only store")
}
rows, err := s.db.QueryContext(ctx, query)
if err != nil {
return nil, nil, err
}
defer rows.Close()
cols, err := rows.Columns()
if err != nil {
return nil, nil, err
}
var out [][]any
for rows.Next() {
vals := make([]any, len(cols))
ptrs := make([]any, len(cols))
for i := range vals {
ptrs[i] = &vals[i]
}
if err := rows.Scan(ptrs...); err != nil {
return nil, nil, err
}
out = append(out, vals)
}
return cols, out, rows.Err()
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

RawSQL has no execution timeout — a slow query can hang the CLI indefinitely.

RawSQL accepts arbitrary user SQL but passes through ctx without any deadline. In commands_query.go line 151, execReadOnlySQL calls it with context.Background(), so there's no upper bound. The agent path (line 103) correctly uses a 2-minute timeout.

Consider adding a default timeout in execReadOnlySQL or documenting that callers must supply a bounded context.

🔧 Proposed fix: add timeout in execReadOnlySQL
 func execReadOnlySQL(cfg Config, query string, jsonOut bool) error {
+	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+	defer cancel()
 	store, err := storage.OpenReadOnly(cfg.DBPath)
 	if err != nil {
 		return err
 	}
 	defer store.Close()
-	cols, rows, err := store.RawSQL(context.Background(), query)
+	cols, rows, err := store.RawSQL(ctx, query)
 	if err != nil {
 		return err
 	}
🧰 Tools
🪛 golangci-lint (2.12.2)

[error] 250-250: Error return value of rows.Close is not checked

(errcheck)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/storage/query.go` around lines 239 - 268, Update execReadOnlySQL to
wrap its context with the same two-minute timeout used by the agent path before
calling Store.RawSQL, ensuring the CLI cannot run arbitrary read-only SQL
indefinitely while preserving RawSQL’s caller-provided context behavior.

…ACUUM, consolidate query helpers

RawSQL executed arbitrary multi-statement input, so a query like
"SELECT 1; PRAGMA query_only=OFF; DELETE FROM events" could flip off
the read-only guard mid-script and delete rows through a "read-only"
Store; RawSQL now rejects any input with more than one statement and
caps results at 10k rows. Prune no longer runs a full VACUUM after
every call (a blocking, whole-file rewrite) unless the caller opts in.
EventsByPID/EventsByPath/EventsBetween are folded into Query via a
Filter with ExactPath/PathLike (now also matching old_path) and
OrderAsc, with basic range/limit validation. Open/OpenReadOnly resolve
to an absolute path before building the file: DSN (a relative path was
silently misparsed as a URI authority) and ping the connection so a
bad DSN or permission problem surfaces at open time.
…add --vacuum

query/engine.go and diff/engine.go move off the removed EventsByPID/
EventsByPath/EventsBetween onto storage.Query with Filter. execReadOnlySQL
gets the same 2-minute timeout the agent path already had, closing a gap
where a user-supplied `ltm sql`/`ltm query sql` could run unbounded.
`ltm prune` gains an explicit --vacuum flag instead of always
compacting. runWatch rejects a non-positive --interval instead of
panicking in time.NewTicker.
…decode errors

newIgnoreRules appended directly to the shared package-level
defaultIgnorePaths slice instead of a copy, risking backing-array
aliasing across calls. flushLoop set ev.SchemaVersion before insert,
but the column isn't persisted and scanEvent recomputes it from
abi.SchemaVersion on read anyway, so the write was a no-op. The eBPF
collector loop now counts and logs malformed kernel events instead of
silently continuing past them.
…erating

Each file the ABI generator produces now carries a sha256 of its own
body in its header comment, so a hand edit that bypasses the generator
(and doesn't update the hash) is detectable without abi.yaml or a
Go/BPF toolchain. `make verify-generated` and a matching unit test
check the embedded hash against current file content.

Also strengthens abi.yaml validation: rejects duplicate (group, event)
tracepoint pairs, duplicate index names, duplicate kernel constants,
and non-positive array counts (whether a literal or a constant
reference), none of which were previously caught.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
internal/storage/query.go (1)

273-313: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

depth tracking in hasMultipleStatements has no effect on the result — likely dead code.

depth is incremented/decremented on (/) but never consulted by the ; branch (or anywhere else), so it doesn't influence the return value for any input. The doc comment implies parenthesis-depth affects false-positive reduction, but no behavior actually depends on it — e.g. removing the '('/')' cases (falling through to default) would produce identical results for every case in TestRawSQLRejectsMultipleStatements.

If there's a scenario meant to be guarded (e.g. some construct where a ; should be tolerated only inside parens), it isn't implemented or tested; otherwise this can be simplified.

♻️ Proposed simplification
 func hasMultipleStatements(sql string) bool {
-	depth := 0
 	for i := 0; i < len(sql); {
 		switch c := sql[i]; {
 		case c == '\'':
 			i = skipQuoted(sql, i+1, '\'')
 		case c == '"':
 			i = skipQuoted(sql, i+1, '"')
 		case c == '[':
 			i = skipUntil(sql, i+1, ']')
 		case c == '-' && i+1 < len(sql) && sql[i+1] == '-':
 			for i < len(sql) && sql[i] != '\n' {
 				i++
 			}
 		case c == '/' && i+1 < len(sql) && sql[i+1] == '*':
 			i += 2
 			for i+1 < len(sql) && !(sql[i] == '*' && sql[i+1] == '/') {
 				i++
 			}
 			if i+1 < len(sql) {
 				i += 2
 			}
-		case c == '(':
-			depth++
-			i++
-		case c == ')':
-			if depth > 0 {
-				depth--
-			}
-			i++
 		case c == ';':
 			if strings.TrimSpace(sql[i+1:]) != "" {
 				return true
 			}
 			i++
 		default:
 			i++
 		}
 	}
 	return false
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/storage/query.go` around lines 273 - 313, Remove the unused depth
variable and the '(' and ')' cases from hasMultipleStatements, since they do not
affect semicolon handling or the function result. Preserve the existing quote,
bracket, comment, and trailing-statement detection behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@internal/storage/query.go`:
- Around line 273-313: Remove the unused depth variable and the '(' and ')'
cases from hasMultipleStatements, since they do not affect semicolon handling or
the function result. Preserve the existing quote, bracket, comment, and
trailing-statement detection behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 88732189-d83d-41a9-8cc2-4708ab0309db

📥 Commits

Reviewing files that changed from the base of the PR and between 16973cf and c177a6e.

⛔ Files ignored due to path filters (2)
  • internal/abi/gen/main.go is excluded by !**/gen/**
  • internal/abi/gen/main_test.go is excluded by !**/gen/**
📒 Files selected for processing (21)
  • AGENTS.md
  • Makefile
  • README.md
  • docs/cli.md
  • internal/abi/kernel_event.gen.h
  • internal/abi/schema_gen.go
  • internal/abi/tracepoints_gen.go
  • internal/cli/commands_query.go
  • internal/cli/commands_runtime.go
  • internal/collector/filter.go
  • internal/daemon/flush.go
  • internal/diff/engine.go
  • internal/ebpf/collector_linux.go
  • internal/query/engine.go
  • internal/storage/maintenance.go
  • internal/storage/open.go
  • internal/storage/query.go
  • internal/storage/schema_gen.go
  • internal/storage/store_test.go
  • internal/storage/types.go
  • internal/storage/write.go
💤 Files with no reviewable changes (1)
  • internal/daemon/flush.go
🚧 Files skipped from review as they are similar to previous changes (13)
  • internal/storage/maintenance.go
  • internal/abi/kernel_event.gen.h
  • internal/abi/schema_gen.go
  • internal/collector/filter.go
  • README.md
  • internal/abi/tracepoints_gen.go
  • internal/storage/open.go
  • internal/ebpf/collector_linux.go
  • internal/storage/write.go
  • internal/cli/commands_query.go
  • Makefile
  • internal/cli/commands_runtime.go
  • AGENTS.md

@Agent-Hellboy Agent-Hellboy merged commit 4a936cc into main Jul 12, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant