Skip to content

SCM observer: thread messenger + populate RepoOriginURL + persist reaction dedup #108

@harshitsinghbhandari

Description

@harshitsinghbhandari

Post-merge cleanup for the SCM observer (#76, merged as 19b6ca5). The observer goroutine runs, persists, and projects to lifecycle, but three wiring gaps still suppress the user-visible nudge behavior end to end. Bundling them because the first two are blocking and the third is a small follow-up that lands cleanest in the same touch.

1) Thread the runtime messenger into the Lifecycle Manager

backend/internal/daemon/lifecycle_wiring.go:38 still constructs the LCM with a nil messenger:

lcm := lifecycle.New(store, nil)

The real runtimeMessenger is built downstream at backend/internal/daemon/daemon.go:87 for startSession. Effect: SCM observer → lifecycle.ApplySCMObservationApplyPRObservationsendOncem.messenger == nil short-circuit at backend/internal/lifecycle/reactions.go:174-176. Every nudge is silently dropped.

Change: move messenger := newSessionMessenger(...) above the startLifecycle call in daemon.go, add messenger ports.AgentMessenger to the startLifecycle signature, replace the nil in lifecycle_wiring.go:38 with the real messenger. ~5 lines.

2) Populate RepoOriginURL at project add (plus lazy backfill)

backend/internal/service/project/service.go:122-128 constructs the project row with RepoOriginURL left as "":

row := domain.ProjectRecord{
    ID:           string(id),
    Path:         path,
    DisplayName:  name,
    RegisteredAt: time.Now(),
}

The schema already has the column (backend/internal/storage/sqlite/migrations/0001_init.sql:11). The SCM observer reads it at backend/internal/observe/scm/observer.go:453, hands "" to provider.ParseRepository, gets false, logs Debug, and skips. In production every registered project is invisible to the observer.

Change: at project-add time run git -C path remote get-url origin and assign to row.RepoOriginURL. Add a lazy backfill in the observer's discoverSubjects (or in service.Get): if RepoOriginURL == "" && Path != "", shell out, persist back, proceed.

Alternative (PR #87's approach): resolve at poll time per project. Easier on the schema but adds a fork-exec per project per poll. Pick A unless we hear otherwise.

3) Persist reaction-dedup signature so nudges survive daemon restart

lifecycle.Manager.react (the seen/attempts maps at backend/internal/lifecycle/reactions.go:14-22) lives on the Manager struct and is lost on restart. After a bounce, every still-failing CI / unresolved review nudges the agent again on the first poll, even if the agent was already prompted before the restart.

Change: add a last_nudge_signature TEXT NOT NULL DEFAULT '' (or similar) column on pr. sendOnce consults the column before sending and updates it after a successful send. Migration is a single ALTER TABLE pr ADD COLUMN.

This part is scoped together with (1) and (2) because all three touch the same end-to-end nudge path; landing them separately invites partial fixes.

Acceptance

  • Daemon constructs LCM with a real messenger (no nil at lifecycle_wiring.go:38).
  • ao project add populates RepoOriginURL from git remote get-url origin.
  • Existing projects with empty RepoOriginURL get backfilled lazily on first observer poll.
  • pr.last_nudge_signature column exists; sendOnce consults + updates it.
  • Unit tests for each of the three changes.
  • go build ./... && go test -race ./... clean.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    lcm-smLifecycle + Session Manager lanescmSCM observer/notifier lane

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions