Skip to content

fix(test): unbreak nightly daemon integration + chaos tests#228

Merged
vishnujayvel merged 1 commit into
mainfrom
fix/daemon-test-socket-isolation-nightly
Jul 8, 2026
Merged

fix(test): unbreak nightly daemon integration + chaos tests#228
vishnujayvel merged 1 commit into
mainfrom
fix/daemon-test-socket-isolation-nightly

Conversation

@vishnujayvel

@vishnujayvel vishnujayvel commented Jul 8, 2026

Copy link
Copy Markdown
Owner

What

Fixes the Nightly Validate workflow, which has been failing every day for
weeks (back to at least June 23). Per-PR CI only runs Tier 0/1 (dagger call test), which excludes the integration/chaos tier, so these failures never
gated a PR and went unnoticed.

The nightly's dagger call validate (chaos + mutation + snapshots) was red
because two integration tests + one chaos test fail. Mutation and snapshots
pass. All fixes are test-only (behind //go:build integration); no
production code is touched.

Root causes (three, stacked in the lifecycle test)

  1. Socket not isolated. DefaultSocketPath is frozen at package init from
    ~/.hookwise, so the tests' t.Setenv("HOOKWISE_STATE_DIR", …) never moves
    it. Since feat: daemon connect-or-start with Unix socket IPC #67 made socket-bind the single-instance authority (SOCKET-1), the
    three daemon tests all bound the shared real ~/.hookwise/daemon.sock and
    collided — with each other under parallel go test ./..., or with a live
    local daemon. Fix: SetSocketPath into a short /tmp dir (matching every
    feeds/bridge daemon test), short enough for the macOS 104-byte
    unix-socket limit.
  2. Weather defaults to disabled. FeedEnabled("weather") reads
    cfg.Weather.Enabled; the lifecycle test passed an empty FeedsConfig{},
    so the daemon skipped the weather producer and weather.json was never
    written. Fix: enable weather in the test config (as a real user must).
  3. ttl_seconds type mismatch. After the JSON cache round-trip it's a
    float64; the assertion compared against untyped-int DefaultTTLSeconds.
    Value is correct (60s → 180 → floored 300); fix: cast to float64, matching
    the sibling assertion.

Test evidence

Full integration + chaos suites green:

GOMEMLIMIT=4GiB go test -race -p 2 -tags integration ./internal/integration/... ./internal/chaos/...
ok  github.com/vishnujayvel/hookwise/internal/integration
ok  github.com/vishnujayvel/hookwise/internal/chaos

Verified with a real daemon holding the default socket and run together
(the exact parallel-daemon collision the nightly hits).

Note (not addressed here)

In production the daemon socket path doesn't honor HOOKWISE_STATE_DIR at
runtime the way the TUI PID does after #227DefaultSocketPath is frozen at
init. That's a separate daemon-coordination question, not needed to green the
nightly, and left for a supervised follow-up.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved test and daemon socket handling to avoid UNIX socket path length issues on macOS and reduce socket collisions during parallel runs.
    • Fixed cache/lifecycle behavior so weather data is generated correctly and cache values are validated more reliably.
  • Tests
    • Updated integration and chaos test coverage to use isolated per-test socket paths and more accurate TTL assertions.

The Nightly Validate workflow (dagger call validate — chaos + mutation +
snapshots) has been red every day for weeks. Per-PR CI only runs Tier 0/1
(dagger call test), which excludes the integration/chaos tier, so these
failures never gated a PR and accumulated silently.

Three stacked, test-only root causes in the daemon tests:

1. Socket not isolated. DefaultSocketPath is frozen at package init from
   ~/.hookwise, so the tests' t.Setenv("HOOKWISE_STATE_DIR", ...) never
   moves it. Since #67 made socket-bind the single-instance authority, the
   three daemon tests (integration x2 + chaos x1) all bound the shared real
   ~/.hookwise/daemon.sock and collided — with each other under parallel
   `go test ./...`, or with a live local daemon. Fix: SetSocketPath into a
   short /tmp dir (matching every feeds/bridge daemon test), short enough to
   stay under the macOS 104-byte unix-socket limit.

2. Weather defaults to disabled. FeedEnabled("weather") reads
   cfg.Weather.Enabled; the lifecycle test passed an empty FeedsConfig{}, so
   the daemon skipped the weather producer and weather.json was never
   written, failing the "both cache files" wait. Fix: enable weather in the
   test config (as a real user must).

3. ttl_seconds type mismatch. After the JSON cache round-trip ttl_seconds is
   a float64; the assertion compared it against the untyped-int
   DefaultTTLSeconds. Value is correct (60s interval -> 180 -> floored 300);
   fix: cast to float64, matching the sibling assertion.

No production code touched; changes are behind //go:build integration.

Test evidence: full integration + chaos suites green under
GOMEMLIMIT=4GiB go test -race -p 2 -tags integration, including run together
(the parallel-daemon collision scenario), with a real daemon holding the
default socket.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EjWqH4FMkdczkSagzXw3dQ
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 65c14b3f-aa35-4426-9e67-cfd4cf13b600

📥 Commits

Reviewing files that changed from the base of the PR and between d7e833c and 4b1f7e9.

📒 Files selected for processing (2)
  • internal/chaos/chaos_test.go
  • internal/integration/integration_test.go

📝 Walkthrough

Walkthrough

This PR adds a shortSocketPath(t) test helper in both chaos and integration test files to create short unix socket paths under /tmp, applies it to daemon setup in several tests to avoid path-length issues and collisions, enables the Weather feed in one test, and fixes a ttl_seconds type assertion.

Changes

Test socket path isolation and fixes

Layer / File(s) Summary
Chaos test socket isolation
internal/chaos/chaos_test.go
Adds shortSocketPath(t) helper and applies it in TestChaos_ProducerPanicRecovery to set the daemon's socket path.
Integration test socket isolation and fixes
internal/integration/integration_test.go
Adds shortSocketPath(t) helper, applies it in daemon lifecycle and analytics DB tests, enables the Weather feed in cache/bridge test config, and fixes the ttl_seconds assertion to compare float64.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: fixing nightly daemon integration and chaos tests.
Description check ✅ Passed The description covers summary, motivation, and test evidence, but it does not follow the template headings or include the checklist section.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 fix/daemon-test-socket-isolation-nightly

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.

@vishnujayvel
vishnujayvel merged commit 637befd into main Jul 8, 2026
6 checks passed
@vishnujayvel
vishnujayvel deleted the fix/daemon-test-socket-isolation-nightly branch July 8, 2026 02:24
vishnujayvel added a commit that referenced this pull request Jul 11, 2026
* test: cover feedData/renderCalendarSegment stale+missing (hw-c195)

Scout hw-xthx (blind spot #4) found the calendar status-line segment had
no direct coverage of its freshness gate: cli_test.go exercises only the
fresh render paths, so a regression in feedData's TTL check (the sole
thing standing between a dead daemon and a permanently-stale calendar
segment) would pass every existing test.

Adds direct unit tests for feedData — malformed/missing envelopes,
default-TTL expiry, custom ttl_seconds override in both directions, and
placeholder-source suppression — and for renderCalendarSegment across
fresh/stale/missing cache plus the next_event shape variants (string,
time-fallback, name-only, empty, unexpected type). The stale case uses
the same payload as the fresh case with only the embedded timestamp
moved, proving omission is caused by the freshness gate alone.

Validation: GOMEMLIMIT=4GiB go test -race -p 2 ./cmd/hookwise/ passes;
golangci-lint clean. Test-only change, no production code touched.

* test: dead-daemon calendar e2e for Go status line (hw-c195)

No integration test covered the calendar feed end-to-end (scout hw-xthx
blind spot #4): TestIntegration_DaemonLifecycleWithCacheBridge registers
only pulse/weather mocks, so the daemon->cache->status-line pipeline for
calendar was validated by inspection only.

Adds an integration-tagged e2e test in cmd/hookwise (it must live here:
renderBuiltinSegment is package main): a real daemon writes a calendar
envelope, the daemon stops, and the test asserts (1) the fresh leftover
envelope still renders the segment — the presence detector that keeps
the omission assertion honest — and (2) after aging the EMBEDDED
timestamp past the daemon-injected ttl_seconds, the segment is omitted
even though rewriting the file makes its mtime current. That
mtime-fresh/content-stale divergence is exactly what a dead daemon
leaves behind; freshness must key on content (bridge.IsEnvelopeFresh),
never mtime. The TTL offset is derived from the on-disk ttl_seconds the
daemon actually injected, not a hardcoded constant.

Honors the #228 isolation contract: HOOKWISE_STATE_DIR=t.TempDir(), and
an explicit short /tmp socket path because DefaultSocketPath is frozen
at package init and ignores the env override. //go:build integration
keeps it in the nightly tier, off the per-PR path.

Validation: GOMEMLIMIT=4GiB go test -race -p 2 -tags integration
./cmd/hookwise/ passes (new test + full package).

* test: add calendar to chaos matrix (hw-c195)

internal/chaos had zero calendar references while weather/pulse were
exercised (scout hw-xthx blind spot #4). Mirrors the existing chaos
patterns for calendar: corrupt-cache-skipped (valid calendar.json
survives corrupt siblings), producer-panic-recovery (a panicking
calendar producer neither crashes the daemon nor blocks sibling feeds,
and leaves no cache file), and a new dead-daemon staleness transition
(envelope fresh right after daemon stop, stale once its embedded
timestamp ages past the daemon-injected ttl_seconds, with file mtime
deliberately fresh to pin content-based freshness).

The panic test enables Calendar in FeedsConfig — calendar is a
recognised feed that defaults to disabled, and a skipped producer would
never exercise the panic-recovery path. Reuses the package's existing
goodProducer/panicProducer/shortSocketPath helpers; #228 isolation
(HOOKWISE_STATE_DIR + explicit socket path) throughout.

Validation: GOMEMLIMIT=4GiB go test -race -p 2 -tags integration
./internal/chaos/ passes (new tests + full package).
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