[bug-hunter] Journald reader panics on Close after journalctl restart failure
- Dominant language
- Go
- Stars
- 12.7k
- Forks
- 5k
- Avg merge
- 2d 15m
- Merged PRs (30d)
- 385
Description
## Impact
When `journalctl` exits and the reader attempts a restart that fails, `Reader.Close()` dereferences a nil `jctl` and panics. This can crash journald ingestion (and potentially terminate the Beat process) instead of surfacing a normal error.
## Reproduction Steps
1. Create and run this standalone repro from repo root:
```go
package main
import (
"context"
"errors"
"fmt"
"strings"
"github.com/elastic/beats/v7/filebeat/input/journald/pkg/journalctl"
"github.com/elastic/beats/v7/filebeat/input/journald/pkg/journalfield"
input "github.com/elastic/beats/v7/filebeat/input/v2"
"github.com/elastic/elastic-agent-libs/logp"
)
type fakeJctl struct {
next func(input.Canceler) ([]byte, error)
}
func (f *fakeJctl) Next(c input.Canceler) ([]byte, error) { return f.next(c) }
func (f *fakeJctl) Kill() error { return nil }
func main() {
ctx := context.Background()
factoryCalls := 0
factory := func(c input.Canceler, logger *logp.Logger, args ...string) (journalctl.Jctl, error) {
for _, a := range args {
if strings.Contains(a, "--version") {
return &fakeJctl{next: func(input.Canceler) ([]byte, error) {
return []byte("systemd 259 (259.3-1-arch)\n"), nil
}}, nil
}
}
if factoryCalls == 0 {
factoryCalls++
return &fakeJctl{next: func(input.Canceler) ([]byte, error) {
return nil, errors.New("journalctl exited with code 42")
}}, nil
}
return nil, errors.New("simulated restart spawn failure")
}
reader, err := journalctl.New(
logp.NewNopLogger(),
ctx,
nil,
nil,
nil,
journalfield.IncludeMatches{},
[]int{},
journalctl.SeekHead,
"",
0,
"",
false,
factory,
)
if err != nil {
panic(err)
}
_, err = reader.Next(ctx)
fmt.Printf("Next returned error: %v\n", err)
fmt.Println("Calling reader.Close() ...")
_ = reader.Close()
}
```
2. Run:
```bash
go run /tmp/gh-aw/agent/repro_journald_nil_close.go
```
## Expected vs Actual
**Expected:** After restart failure, `Next()` should return an error and `Close()` should safely return an error (or nil), without panic.
**Actual:** `Close()` panics with nil-pointer dereference.
Observed output:
```text
Next returned error: cannot restart journalctl: simulated restart spawn failure
Calling reader.Close() ...
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x18 pc=0x685a38]
goroutine 1 [running]:
github.com/elastic/beats/v7/filebeat/input/journald/pkg/journalctl.(*Reader).Close(...)
filebeat/input/journald/pkg/journalctl/reader.go:313
```
## Failing Test
A minimal test can mock factory behavior to:
- return a working `Jctl` initially,
- return `(nil, err)` on restart path,
- assert `reader.Next(...)` returns `cannot restart journalctl`,
- then assert `reader.Close()` does not panic.
## Evidence
- `filebeat/input/journald/pkg/journalctl/reader.go:301-304` assigns `r.jctl = jctl` even when `newJctl` returns error.
- `filebeat/input/journald/pkg/journalctl/reader.go:370-373` restart path can return `cannot restart journalctl`.
- `filebeat/input/journald/pkg/journalctl/reader.go:313` `Close()` unconditionally calls `r.jctl.Kill()`.
- `filebeat/input/journald/input.go:221` uses `defer reader.Close()`, so the panic is reachable on error returns from read loop.
> [!NOTE]
>
> 🔒 Integrity filtering filtered 1 item
>
> Integrity filtering activated and filtered the following item during workflow execution.
> This happens when a tool call accesses a resource that does not meet the required integrity or secrecy level of the workflow.
>
> - issue:elastic/beats#unknown (`search_issues`: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".)
>
>
---
[What is this?](https://ela.st/github-ai-tools) | [From workflow: Bug Hunter](https://github.com/elastic/beats/actions/runs/23742207004)
Give us feedback! React with 🚀 if perfect, 👍 if helpful, 👎 if not.
> - [x] expires on Apr 6, 2026, 11:39 AM UTC
Contributor guide
Research direction
Start in filebeat/input/journald/pkg/journalctl/reader.go, especially the restart path around lines 301-304 and Close around line 313; review filebeat/input/journald/input.go to understand deferred cleanup. Add focused regression coverage for a failed restart, verifying Next returns the restart error and Close completes without a panic.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- backend, operating-systems
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100