Skip to content

fix(hub): surface silently swallowed errors in hubsync hook and replication loop#114

Closed
omergk28 wants to merge 1267 commits into
ActiveMemory:mainfrom
omergk28:fix/100-hub-silent-error-suppression
Closed

fix(hub): surface silently swallowed errors in hubsync hook and replication loop#114
omergk28 wants to merge 1267 commits into
ActiveMemory:mainfrom
omergk28:fix/100-hub-silent-error-suppression

Conversation

@omergk28

Copy link
Copy Markdown
Contributor

Summary

Two hub code paths swallowed errors with no logging surface — the session-start hubsync hook (internal/cli/system/core/hubsync) and the cluster replication loop (internal/hub/replicate.go). Operators couldn't tell a healthy quiet sync from one that never reached the network.

This PR wires every silent return through the established warn sink with format constants in config/warn, and un-conflates the sync-error check from the empty-result check (only the error warns now). Both functions keep their signatures and never-block contracts — logging is the only behavior change.

Closes #100.

Already landed

Two of #100's sub-items were already in main before this PR (the conn.Close defer warn and the store.Append warn + keep-consuming, via CloseHubClient/HubReplicateAppend). This PR covers the rest and adds the regression tests the issue asked for — including pinning the already-landed keep-consuming behavior, which previously had no coverage.

One deliberate deviation from the issue's proposed code

The issue's sketch warns on every RecvMsg error — but io.EOF is the normal end of every sync stream, so that would emit a warning once per ReplicateInterval on perfectly healthy replication. The receive path here stays silent for eof() (the hub package's own end-of-stream helper, same as the other two RecvMsg sites) and for caller shutdown (ctx.Err() != nil), and warns on everything else. TestReplicateOnce_CleanReplicationDoesNotWarn pins this.

Tests (mapped to the issue's Tests Required)

Issue ask Test
Warns on load error TestSync_WarnsOnLoadError
Warns on dial error TestSync_WarnsOnDialError — empirically, the only eager grpc.NewClient failure is a control character failing URL parse; every other malformed target (://invalid…) defers to first use. TestSync_WarnsOnPullError covers the lazy path (closed port → Sync RPC fails)
No warn on empty result (the un-conflation) TestSync_NoWarnOnEmptyResult — real in-process hub, zero entries, asserts an empty warn buffer
Replication warns on dial error TestReplicateOnce_WarnsOnDialError + TestReplicateOnce_WarnsOnTransportError
Keeps consuming after append error TestReplicateOnce_KeepsConsumingAfterAppendError — read-only follower dir, asserts exactly 2 append warnings (root/Windows skip guards per the rc/require_test.go precedent)

All use the existing warn.SetSink seam; the hubsync package previously had zero tests.

Verification

  • go test -race -count=2 on both packages — clean.
  • Mutation checks: three deliberate regressions each fail their pinning test — (1) re-conflating syncErr with the empty check fails TestSync_NoWarnOnEmptyResult; (2) removing the EOF guard fails TestReplicateOnce_CleanReplicationDoesNotWarn; (3) dropping the dial warn fails TestSync_WarnsOnDialError.
  • make audit — all checks passed.

🤖 Generated with Claude Code

parlakisik and others added 30 commits April 9, 2026 08:58
When --include-shared is passed, loads .context/shared/*.md and
includes them as Tier 8 in the agent context packet. Shared
entries are budget-aware and rendered in both markdown and JSON
output formats. No-op when .context/shared/ is absent (opt-in).

Signed-off-by: Murat Parlakisik <parlakisik@gmail.com>
Background hub operation: --daemon forks the server and writes
a PID file to <data-dir>/hub.pid. --stop sends SIGTERM to the
running daemon and removes the PID file. Exec and error logic
split to internal/exec/daemon/ and internal/err/serve/ per
project conventions.

Signed-off-by: Murat Parlakisik <parlakisik@gmail.com>
Raft-lite: uses hashicorp/raft ONLY for master election, not
data consensus. No-op FSM since entries are replicated via
sequence-based gRPC sync. New --peers flag on ctx serve --shared
for cluster membership. Single-node mode auto-bootstraps.

Signed-off-by: Murat Parlakisik <parlakisik@gmail.com>
Followers replicate from the master via sequence-based gRPC
sync with automatic retry. Failover client tries peers in order
and verifies connectivity with a Status call before returning.

Signed-off-by: Murat Parlakisik <parlakisik@gmail.com>
Cluster management CLI: ctx hub status shows role and entry
counts, ctx hub peer add/remove manages cluster membership,
ctx hub stepdown transfers leadership gracefully.

Signed-off-by: Murat Parlakisik <parlakisik@gmail.com>
Four integration tests: publish-and-sync across two clients,
incremental sync with since_sequence, type-filtered sync, and
full Client library round-trip (register, publish, sync, status).

Signed-off-by: Murat Parlakisik <parlakisik@gmail.com>
New pages: connect.md (register, subscribe, sync, publish,
listen, status), serve.md (shared hub, daemon, cluster modes),
hub.md (status, peer, stepdown). Updated agent docs with
--include-shared flag and Tier 6-8 budget tiers. Updated CLI
index with new command entries.

Signed-off-by: Murat Parlakisik <parlakisik@gmail.com>
When --share is passed, ctx add writes the entry locally AND
publishes it to the shared hub in one step. Best-effort: hub
publish failure does not block the local write. Uses the
existing encrypted connection config from ctx connect register.

Signed-off-by: Murat Parlakisik <parlakisik@gmail.com>
Failover: first-peer, skip-bad-peer, all-bad-peers. Fan-out:
subscribe-broadcast, unsubscribe, broadcast-to-none. Renderer:
creates files with origin tags, appends to existing, filename
generation. Total hub test count: 26.

Signed-off-by: Murat Parlakisik <parlakisik@gmail.com>
New check-hub-sync hook runs on UserPromptSubmit, daily
throttled. If .connect.enc exists, silently syncs new entries
from the hub to .context/shared/. No manual ctx connect sync
needed after initial registration.

Signed-off-by: Murat Parlakisik <parlakisik@gmail.com>
1. Fix listen command: now streams via Listen RPC instead of
   blocking after initial sync
2. Add input validation on Publish: type, ID, origin, content
   size limit (1MB)
3. Warn on --share publish failure instead of silent suppression
4. Constant-time token comparison via crypto/subtle + O(1) map
   lookup
5. Wire Raft cluster to Server with SetCluster/Shutdown
6. Reject duplicate project registration in store
7. Disconnect slow fanout listeners instead of silently dropping
8. File locking on sync state to prevent concurrent race
9. Fail fast on auth errors in failover client

Signed-off-by: Murat Parlakisik <parlakisik@gmail.com>
Document --share flag behavior (best-effort, warns on failure),
auto-sync hook, input validation rules (1MB content limit),
and duplicate registration rejection.

Signed-off-by: Murat Parlakisik <parlakisik@gmail.com>
Rebase onto main brought strict audit tests (doc structure, magic
strings/values, dead exports, flag YAML drift, fmt.Fprintf checks)
that the hub code predates. Grandfather hub violations, add package
exemptions, fix fmt.Fprintf return check, add AdminAuth flag
constant, and fix AddConfig.Share field placement.

Spec: specs/shared-hub-federation.md
Signed-off-by: Murat Parlakisik <parlakisik@gmail.com>
…ntity

Closes TASKS.md:45 ("Move 6 grandfathered cross-package MCP types to
entity/") by eliminating the Handler struct entirely and reducing the
grandfathered cross-package types count from 6 to 0.

## Phase 1 — sub-task moves

- `def/prompt.EntrySpec`/`EntryField` -> `entity.PromptEntrySpec`/
  `PromptEntryField` (pure data, natural entity fit)
- `internal/mcp/session/*` collapsed into `internal/mcp/handler/*`
  (sole consumer; eliminates the sibling crossing)
- `internal/mcp/server/poll/` -> `internal/mcp/server/dispatch/poll/`
  (ancestor->descendant exemption closes the last sibling crossing
  before Phase 2)

## Phase 2 — flatten the Handler god-object

The Handler struct was a parameter bundle masquerading as domain
logic. 13 of its 17 methods only read ContextDir; 2 mutated session
state; the other 2 did not touch the receiver at all. TokenBudget
was a pass-through passenger consumed only by dispatch.go.

- New `entity.MCPDeps { ContextDir, TokenBudget, *MCPSession }`
  parameter object, held by the server and threaded through dispatch
- New `entity.MCPSession` holding the former session.State fields
  with pure Record*/Increment* mutation methods (no I/O)
- New `entity.PendingUpdate` (pure data)
- All 17 `(h *Handler) Foo(...)` methods converted to free functions
  `handler.Foo(d *entity.MCPDeps, ...)`
- `handler.CheckGovernance` kept as a free function in handler/
  because it still reads .context/state/violations.json (Phase 3
  absorbed into Phase 2 naturally)
- `server.Server.handler *handler.Handler` replaced with
  `server.Server.deps *entity.MCPDeps`; route/tool/* and
  route/prompt/* updated to take `*entity.MCPDeps`
- `handler.Handler` struct deleted entirely; `handler/handler.go`
  deleted; `handler/state.go` deleted
- `governance_test.go` rewritten to construct `*entity.MCPDeps`

## Grandfather purge (TestTypeFileConvention)

The grandfatheredTypes map was cleared to zero entries. All 35
underlying violations across 18 packages were fixed by moving type
declarations into per-package types.go files; methods remain in
their original files, which become type-decl-free and are skipped
by the audit.

- New types.go created in 13 packages (cli/dep/core/{golang,node,
  python,rust}, cli/drift/core/{fix,out}, cli/guide/core/skill,
  cli/remind/core/store, cli/system/core/nudge, cli/why/core/data,
  config/memory, mcp/server/catalog, mcp/server/io)
- fm.go renamed to types.go (frontmatter) and atom.go renamed to
  types.go (rss) since they were pure type definition files
- session.go regenerated with only the Session interface
- parser.{ClaudeCode,Copilot,CopilotCLI,MarkdownSession} and
  lookup.commandEntry absorbed into their packages' existing types.go

Hardened the grandfatheredTypes comment with an explicit agent
directive: do not add entries here under any circumstance. Any
re-addition requires a dedicated pull request with per-entry
justification and maintainer approval. Drive-by additions by any
assistant are unauthorized.

## Interface segregation

Interfaces are behavioral contracts, not data blueprints, so they
live in their own <name>.go files rather than types.go.

- TestTypeFileConvention updated: interface type declarations are
  now exempt from the "must have exported receiver" phase 4 check.
  Interfaces cannot carry receivers; a file containing only an
  interface is a valid pure type file.
- `parser.Session` interface moved to journal/parser/session.go
- `builder.GraphBuilder` interface moved to
  cli/dep/core/builder/graph_builder.go

## Audit state

- TestCrossPackageTypes: grandfathered 6 -> 0
- TestTypeFileConvention: grandfathered map 34 -> 0
- TestDocCommentStructure: 0 (unchanged)
- make test: all pass
- make lint: 0 issues

## Documentation

Updated .context/DETAILED_DESIGN-mcp.md, .context/ARCHITECTURE.md,
.context/DANGER-ZONES.md, and .context/DETAILED_DESIGN.md to reflect
the new package layout (no more mcp/session, poll lives under
dispatch, Handler replaced by entity.MCPDeps).

Also sweeps in two pre-existing .context/ entries that were
uncommitted at session start (architecture skill pipeline decision,
pad index shifting learning).

Spec: specs/mcp-handler-flatten.md

Signed-off-by: Jose Alekhinne <jose@ctx.ist>
…ext-hub

feat: shared context hub for cross-project knowledge sharing
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
Add parallel-slice batch functions to flagbind (BindStringFlagsP,
BindStringFlags, BindBoolFlags, BindBoolFlagsP, BindStringFlagShorts,
BindStringFlagsPDefault) replacing repetitive one-at-a-time flag
registrations across 8 CLI command files.

Convention sweep:
- hub: rename files (entry_validate→validate_entry, errcheck→err_check),
remove const aliases, move magic numbers to config/hub and config/entry,
fix predicate naming (isEOF→eof, isAuthErr→authErr)
- initialize: rename claudecheck→claude_check, details→detail
- steering/types: align docstrings with conventions
- compliance: fix TestNoSecretsInTemplates false positive on YAML keys
- golangci: extend G101 exclusion to all embed/text/ DescKey files
- sysinfo: add missing nolint:gosec for G204 on vm_stat

Spec: specs/flagbind-batch-and-convention-sweep.md
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
…arser rescue

The dead exports test maintained a manual list of platform-specific
symbols (linux-only, darwin-only) that appeared dead on the other OS.
This list grew with every new platform constant and taught bad habits.

Replace with a go/parser sweep that parses ALL .go files regardless
of build tags, extracts selector names, and automatically rescues
any export referenced from any platform file. No manual maintenance.

Spec: specs/flagbind-batch-and-convention-sweep.md
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
…next

refactor: flagbind batch helpers and convention sweep
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2 to 3.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](softprops/action-gh-release@v2...v3)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-version: '3'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.43.0 to 0.44.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](golang/tools@v0.43.0...v0.44.0)

---
updated-dependencies:
- dependency-name: golang.org/x/tools
  dependency-version: 0.44.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Five command surface changes:

- Move `ctx bootstrap` back to `ctx system bootstrap` (hidden,
  agent-only plumbing that was incorrectly promoted to top-level)
- Rename `ctx stats` to `ctx usage` (clearer intent)
- Rename `ctx resource` to `ctx sysinfo` (matches internal package,
  unambiguous)
- Remove `ctx dep` entirely (marginal utility, go list/npm ls suffice)
- Introduce `ctx hook` parent — consolidates message, notify, pause,
  resume, and event under a single namespace

Includes full propagation: constants, YAML assets, flag/text DescKeys,
group.go registration, docs, recipes, skills, CLI index, zensical.toml
nav, and dead export cleanup of orphaned dep support packages.

Also adds:
- _ctx-command-audit skill for post-rename quality gates
- Package taxonomy section in docs/home/contributing.md
- hack/agents/ directory for autonomous agent prompts
- Follow-up task for ctx explore command

Signed-off-by: Jose Alekhinne <jose@ctx.ist>
…olution

When a non-ctx-initialized repo lives inside a ctx-initialized parent
workspace, walkForContextDir walked upward and found the parent's
.context, then the boundary check rejected it. Now the walk validates
candidates against the git root: if a .context is found outside the
git root, it belongs to a different project and is discarded. Git is
a hint, not a requirement — when no .git exists, the walk falls back
to CWD.

Changes:
- Add findGitRoot helper to walk.go
- Update walkForContextDir to validate candidates against git root
- Add DotDir constant to config/git, consolidate Diff into subcommands block
- Add walk_test.go with 9 edge-case tests (nested repos, worktrees, no git)
- Update existing UpwardWalkFromSubdir test to include .git directory

Spec: specs/walk-boundary-git-anchor.md
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
…sitives

The 1-minute load average spikes during normal build/test runs,
triggering false "critically low resources" alerts. The 5-minute
average smooths transient spikes while still catching sustained
resource pressure.

Spec: specs/load-alert-5min-average.md
Signed-off-by: Jose Alekhinne <jose@parlakisik.com>
When ctx is installed as a global plugin, its hooks fire in every
project Claude opens — including non-ctx projects. Two relay hooks
were missing the state.Initialized() guard and nagged users with
"Load Xx CPU count" and backup-age warnings in projects that don't
use ctx at all.

Add the guard, matching the pattern already present in 18 other
hooks. Safety hooks (block_dangerous_command, block_non_path_ctx)
intentionally run regardless of ctx state.

Also records this session's decisions and learnings, and updates
TASKS.md line 30 to reflect partial progress (boundary side effect
resolved in e24941d; remaining hooks need per-hook audit).

Spec: specs/hook-guard-uninitialized.md
Signed-off-by: Jose Alekhinne <jose@parlakisik.com>
Port all 40 Claude skills to Copilot CLI format with proper YAML
frontmatter (tools array instead of allowed-tools string). Includes
lifecycle hook scripts (bash + PowerShell), agent instructions, and
hook configuration.

Contents:
- 40 skill SKILL.md files under integrations/copilot-cli/skills/
- 8 hook scripts (session-start/end, pre/post-tool-use × bash/ps1)
- INSTRUCTIONS.md agent bootstrap instructions
- ctx-hooks.json lifecycle hook configuration
- Updated embed.go to include integrations assets
- Parity spec document (specs/copilot-feature-parity-kit.md)

Signed-off-by: ersan bilik <ersanbilik@gmail.com>

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: ersan bilik <ersanbilik@gmail.com>
Combines two in-flight workstreams into one commit.

## Docstring namespace sweep (parallel agent)

Follow-up to the CLI namespace cleanup in 78fbdf7. Updates
doc comments, blog posts, recipes, and the regenerated site to
reflect current command names (e.g. `ctx pause` → `ctx hook pause`,
`ctx resume` → `ctx hook resume`, `ctx message` → `ctx hook message`).
Also refreshes the Copilot CLI integration skills added in
edaac81 (PR ActiveMemory#63) to match the new namespace.

Scope: ~450 files across docs/, site/, internal/cli, internal/write,
internal/config, .github/, integration skill templates, and .claude/
skill SKILL.md files.

## Git push regex hardening (this session)

The `block-dangerous-command` hook's `MidGitPush` regex only matched
`git push` mid-command after `;`, `&&`, or `||`. This session
accidentally bypassed it with `git -C <path> push` — the permissions
deny list `Bash(git push *)` only matches prefix `git push`, so
`git -C <path> push` also slipped through.

Replace `MidGitPush` with a broader `GitPush` that covers:

- Bare `git push` at command start
- All separator and subshell entry points (`;`, `&&`, `||`, `|`, `&`,
  `(`, `$(`, backtick, newline)
- Env-var and command-wrapper prefixes (`GIT_DIR=/x git push`,
  `time git push`, `nice git push`)
- Any flag shape between `git` and `push` (`-C path`, `-c key=val`,
  `--git-dir=/path`, `--no-pager`, `--bare`, `-p`, `-P`)
- Tail anchor that distinguishes subcommand from ref names
  (`push-to-remote`, `push_branch`) via `[^a-zA-Z0-9._/-]|$`

Documented trade-offs: accepted false positives on `git log push`
and `git commit -m push` (push as literal arg); known blind spots
for `eval` / `sh -c` quoting and shell aliases.

Adds `internal/config/regex/cmd_test.go` with 42 table-driven cases
covering all entry points, flag shapes, prefixes, negative cases
(other subcommands, ref-name continuations), and the accepted
false-positive classes.

Renames the Go symbol `MidGitPush` → `GitPush` to accurately
reflect scope; keeps legacy variant string `mid-git-push` and text
key `block.mid-git-push` (user-facing message is already generic:
"git push requires explicit user approval").

Spec: specs/git-push-regex-hardening.md
Spec: specs/cli-namespace-cleanup.md
Signed-off-by: Jose Alekhinne <jose@parlakisik.com>
…-skill-parity-rebased

Feat/copilot cli skill parity rebased
…dules/golang.org/x/tools-0.44.0

deps: Bump golang.org/x/tools from 0.43.0 to 0.44.0
josealekhine and others added 22 commits June 1, 2026 19:38
Data-path marshal errors are now returned instead of writing empty
output: the vscode tasks/extension/mcp config writers, the copilot
vscode MCP writer, and the blocknonpathctx hook response.

Two discards that lose results are now logged: journal querying skips
an unreadable session dir via logWarn (cfgWarn.JournalScanDir) instead
of dropping its sessions silently; drift's post-fix re-check warns on a
failed context reload (cfgWarn.DriftReload) and keeps the prior context
instead of detecting against an empty one.

The genuinely-acceptable discards are annotated rather than changed:
steering yaml.Marshal of plain frontmatter structs (cannot fail;
formatters return []byte by contract), the trigger-test input echo,
best-effort hook-stdin Unmarshal, the opencode path lookup whose error
is handled by an empty-path fallback, and the sourcecoverage date parse
that degrades to zero time on a ctx-generated column.

Spec: specs/error-handling-audit.md
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
Make the remaining `_ =` discards that EH.5's grep flags explicitly
intentional, so an auditor reads them as deliberate rather than lazy:

- cobra MarkFlagRequired / RegisterFlagCompletionFunc: only error on
  an unregistered flag name, bound immediately above (unreachable)
- trace.Record / TruncatePending: best-effort provenance that must
  never fail the user's command
- filepath.Glob in the stats watcher: nil-safe on a malformed pattern
- mcp poller WriteJSON: best-effort notification push, no return path
- hub cluster.Shutdown: best-effort graceful-stop teardown

After this, `grep -rn '_ =' internal/` resolves to only category-(d)
fmt.Fprint output and annotated/handled sites (EH.5 DoD).

Spec: specs/error-handling-audit.md
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
Mark the Phase EH fix/validate tasks done with per-task ship notes.
The sweep surfaced or justified every error discard under internal/:
data-path errors returned, best-effort discards routed to logWarn,
write-handle closes merged into named returns, and the genuinely
acceptable sites annotated. EH.5 grep clean, lint + test green.

Spec: specs/error-handling-audit.md
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
Capture the EH-sweep lesson: an auto-generated error-discard
catalogue classified by callee name / regex is a worklist of
candidates, not findings. Read the discarded value's type and the
call's role before fixing — name-inference produced three false
positives this pass (MergePublished bool, LoadState value, io/security
already-correct closes).

Spec: specs/error-handling-audit.md
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
…d-index-data-loss

Fix/learning add index data loss
…verable webhooks

Two real defects behind the "notify fails in worktrees" report (P0.8.5):

- crypto.ResolveKeyPath auto-detected a project-local <contextDir>/.ctx.key
  and preferred it over the global key. That file is gitignored, so a fresh
  worktree resolved to a different key and decryption silently failed. Remove
  the tier: resolution is now key_path override > global, with project-local
  kept only as a degenerate fallback when no home dir exists (never
  auto-detected). Also a documented security antipattern (key next to
  ciphertext).
- notify.Send swallowed every fire-path failure as a silent no-op. It now
  treats .notify.enc existence as the sole "configured" signal and warns
  (non-fatal) when a configured webhook cannot be delivered — bad/absent key,
  decrypt, marshal, or POST — while keeping legitimate silences (not
  configured, event not subscribed).

LoadWebhook detects file absence via os.Stat + errors.Is, not os.IsNotExist,
which does not unwrap the text-registry-wrapped error.

Spec: specs/notify-resolution-hardening.md
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
`gitnexus analyze` injects a full "# GitNexus — Code Intelligence" block
between <!-- gitnexus:start/end --> markers. It first landed in 6afb50d
(a recall/core deletion commit) as an analyze side effect, not a deliberate
choice, and has churned on every reindex since.

The project already has a curated home for this: GITNEXUS.md, added
deliberately in bf42b1f with a CLAUDE.md cross-reference. The injected
blocks were pure duplication on top of it.

Realign to the pre-injection canonical state:
- AGENTS.md: back to the redirect-to-CLAUDE.md stub (its form since fda3c82)
- CLAUDE.md: keep the Companion Tools pointer to GITNEXUS.md, drop the block

Re-injection guard lives outside this repo: run analyze with --skip-agents-md
so the global gitnexus hook stops rewriting these two files.

Spec: specs/meta/chores.md
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
Follow-up to 8da165a: the marker-bounded removal of the GitNexus block
from AGENTS.md/CLAUDE.md is mechanical, so capture a Phase CT task to
automate it as `make strip-gitnexus`.

Spec: specs/meta/chores.md
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
Land the ctx-dream design that has been in flight: a scheduled,
standalone "dream" that only ever proposes (Option B), human-gated via
serendipity, with v1 scoped to disciplined triage of the gitignored
ideas/ folder.

- specs/ctx-dream.md: the v1 spec, plus a sketched-not-contracted v2
  section capturing the end-to-end pipeline (raw episodes -> evidence
  store -> dream pass -> typed durable artifacts), a cautionary-sibling
  comparison against the Hermes "Dreaming" design (un-gated autonomous
  promotion is the failure mode to avoid), the lifted attention-scoring
  rubric, and the quiet_minutes trigger gate.
- .context/briefs/...-ctx-dream-disciplined-consolidator.md: the debated
  brief the spec consumes.
- .context/TASKS.md: ctx-dream v1 task breakdown (guards, ledger/state,
  triage, review CLI + serendipity skill, executor, tests).

Spec: specs/ctx-dream.md
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
Consolidation pass per the 3:1 discipline. LEARNINGS 157 -> 81 (18
merged entries) and DECISIONS 111 -> 56 (13 merged entries); every
original is preserved verbatim under .context/archive/ and the indexes
were rebuilt. No unique fact or rationale dropped — merges distill to
bullets, originals are archived, not deleted.

Includes the in-flight ctx-dream knowledge captured this cycle: the
ctx-dream decision (kept standalone/legible) and the dream design
principles (folded into a single "ctx-dream design principles"
learning), so the dream knowledge rides with the consolidation that
absorbed it.

Also archives a handful of point-in-time/superseded entries
(IMPLEMENTATION_PLAN refs, old key-migration specifics, Claude Code
v2.1.69 internals, PR ActiveMemory#27 merge-readiness, blog-first ordering).

Spec: specs/consolidation-nudge-hook.md
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
… fields

Claude Code >= 2.1.158 emits attributionMcpServer / attributionMcpTool
(MCP invocation provenance) and >= 2.1.161 emits promptSource (prompt
provenance). These were unknown to the importer, so `ctx journal import`
and `ctx journal schema check` reported schema drift on every recent
session — recurring noise that erodes the drift signal.

Add the three fields to OptionalFields (versions read from the actual
JSONL, not guessed) and pin all three in TestKnownField_PostV1FieldDrift
so a future refactor can't silently drop them and reopen the drift.

Spec: specs/fix-journal-schema-drift.md
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
…ased)

ctx drift's checkSyncStaleness hardcodes cursor/cline/kiro and treats a
missing synced output as stale, so a single-tool project (e.g. Claude
only) is nagged to sync steering for editors it never uses — pure noise.

Spec replaces the hardcoded trio with a presence-based set: a syncable
tool is checked iff its native output already exists on disk. This
serves both audiences with one mechanism — a multi-tool repo that tracks
its outputs gets them enforced in every clone; a single-tool user who
never synced sees nothing. Repo-side freshness moves to a wired
sync-steering / check-steering make target. No new .ctxrc surface; the
pre-existing unused steering.default_tools (a content-scoping concern)
is left out of scope.

Spec: specs/steering-sync-drift-respects-configured-tools.md
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
Generated by `ctx steering sync --all` from .context/steering/. This
repo's dev team is multi-tool, so committing these outputs means a fresh
clone has them present and the presence-based drift check enforces their
freshness across all three tools (see the steering-sync-drift spec).
Repo policy, not a feature requirement.

Spec: specs/steering-sync-drift-respects-configured-tools.md
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
…steering

Implements specs/steering-sync-drift-respects-configured-tools.md.

checkSyncStaleness no longer hardcodes cursor/cline/kiro and no longer
treats a missing output as stale — so a single-tool project (e.g.
Claude-only) is no longer nagged to sync steering for editors it never
uses. A syncable tool is now checked iff it is "in play": at least one
of its native outputs already exists on disk.

- internal/steering: add SyncableTools() (the cursor/cline/kiro set) and
  Synced(steeringDir, projectRoot, tool) (file-level presence via
  SafeStat, skipping tombstoned/excluded files).
- internal/drift: checkSyncStaleness iterates SyncableTools() and skips
  any tool that is not Synced().
- Makefile: standalone `sync-steering` + `check-steering` (the latter
  wired into `audit`, run by `check`), both via `go run`. sync-steering
  is intentionally not a build prereq — steering outputs are repo-root
  artifacts, not embedded assets, so they must not be regenerated on
  every build.
- Tests: Synced/SyncableTools units; drift cases for "unsynced tools not
  checked" (the headline fix) and "only synced tools checked".
- Spec: corrected the build-wiring section to match (check in audit,
  sync standalone).

Spec: specs/steering-sync-drift-respects-configured-tools.md
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
…r contract

Resolve the ctx-dream v1 open questions ahead of implementation:

- Executor is a documented contract, not a hardcoded assumption. cron
  `claude -p` is the reference executor (guards as PreToolUse hooks for
  free), but ctx owns an executor-agnostic Go core (dreams/ layout,
  state, ledger, proposal schema, guards as callable logic) so other
  harnesses can run the same dream. New Executor-contract section +
  decision record.
- Dream is opt-in (not enabled by default); a `dream:` .ctxrc section
  gates it. Two user-facing docs required: a Claude Code enablement
  guide and an executor-contract reference for other harnesses (added
  to TASKS).
- Serendipity review split into its own spec (specs/ctx-serendipity.md):
  the human garden-walk gate, accept/reject/amend, mechanical-vs-
  generative dispositions, ctx remind cadence, routing to archive /
  /ctx-spec / /ctx-blog.
- Settled: merit defaults via the attention-scoring rubric (ranking
  only, never an auto-promote threshold); summary reuses the session
  model; Go package layout follows convention; proposals/ledger carry
  stable IDs so v2 supersession isn't foreclosed.

Records the executor-contract architectural decision in DECISIONS.md.

Spec: specs/ctx-dream.md
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
…ledger

The contracted Go core of ctx-dream (specs/ctx-dream.md), with no CLI,
skill, or executor yet — the safety substrate landed first, fully tested.

- internal/config/dream: proposal status/action enums, confidence,
  source-status, ledger decision, notebook file names + validation
  reason templates (config-housed, no magic strings).
- internal/dream: the data contract (Proposal, SourceState, LedgerEntry,
  GuardDecision) plus the engine —
  - guards: WriteScope (writes only under dreams//ideas/, or specs/ on
    promote) and Leak (target must be git-ignored, except the promote
    crossing) — executor-agnostic, so a Claude Code PreToolUse hook and a
    raw-API tool executor enforce the same invariant.
  - state: hash-based delta selection (the discipline clock) + JSON
    persistence at dreams/state.json.
  - ledger: append-only dreams/ledger.md + dedup-against-seen.
  - validation referencing every enum value (the dead-export-safe fix).
- internal/err/dream + config/embed/text/err_dream.go + errors.yaml: the
  error/text-registry ceremony for the above.
- internal/exec/git + config/git: CheckIgnore helper (maps git
  check-ignore exit 0/1 to a bool, surfaces only real failures) — the
  don't-leak primitive.
- config/dir: Dreams, Done directory constants.

Tests cover the guard allow/deny matrix (incl. tracked-path leak refusal
and the specs-promote crossing), state round-trip + delta selection, and
ledger append/readback + dedup.

Spec: specs/ctx-dream.md
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
…ions

The runnable Go surface on top of the v1 foundation (specs/ctx-dream.md).

- internal/cli/dream: `ctx dream` (run a bounded pass) + review / accept /
  reject / amend subcommands, registered in the sessions command group.
  The pass resolves the root, ensures the gitignored dreams/ dir, runs
  the opt-in trigger gate (enabled + cadence + quiet-window, bypassable
  with --force), DeltaSelects ideas/**.md, takes an exclusive lock,
  invokes the configured executor (default `claude -p` with the ctx-dream
  skill + --max-turns/--model), FAILS LOUD with a failmark on a missing
  or failed executor, then validates proposals and persists state.
- internal/dream: the disposition appliers — scan/delta, proposal
  read+pending (dedup-against-seen), and accept/reject/amend/backup.
  Every ideas/ mutation passes BOTH guards before any write; mechanical
  actions (archive→ideas/done/, mark-blog, keep) run in Go, generative
  ones (promote/merge) record intent and route to /ctx-serendipity.
- internal/write/dream: all user-facing output. internal/exec/dream:
  executor lookup/run (keeps exec.* in internal/exec). internal/io:
  SafeTryLock/SafeUnlock (portable O_EXCL lock).
- internal/rc: the `dream:` .ctxrc section (enabled/mode/max/cadence/
  quiet_minutes/model/budget/executor) + schema; bootstrap registration;
  config/text/flag registry entries.

Proposal on-disk contract: a single JSON array at dreams/<ts>/proposals.json.

Spec: specs/ctx-dream.md
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
…tignore

The harness wiring + cognition that makes v1 runnable, plus the human gate.

- internal/assets/claude/skills/ctx-dream: the disciplined ideas/-triage
  skill (classify + ground against code/specs, emit provenance-bearing
  proposals into dreams/<ts>/proposals.json) — scoped to the spec's v1,
  not the broader draft. Bundles guard.sh, the PreToolUse hook that
  restricts a headless pass to writing under dreams/ (mirrors the Go
  WriteScope/Leak guards for the Claude Code executor path).
- internal/assets/claude/skills/ctx-serendipity: the garden-walk review
  skill — drives ctx dream review/accept/reject/amend; mechanical
  dispositions apply instantly, generative (merge/promote) are done from
  the full source with backup-before-mutate (specs/ctx-serendipity.md).
- docs/recipes/run-the-dream.md: opt-in enablement guide for Claude Code
  users (.ctxrc dream.enabled, cron entry, guard-hook settings, review).
- docs/reference/dream-executor-contract.md: the contract for non-Claude-
  Code harnesses (bounded pass, structural guard enforcement, fail-loud,
  proposals-only-into-dreams/).
- .gitignore: dreams/ (inherits ideas/'s privacy class; the don't-leak
  guard double-checks at write time).
- TASKS.md: mark the completed ctx-dream v1 items.

Spec: specs/ctx-dream.md
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
…d site

Close the user-facing surface for ctx-dream per the CONVENTIONS
"User-Facing Surface Completeness" checklist:

- docs/cli/dream.md: per-command reference (the pass + review/accept/
  reject/amend, flags, examples), linking the run-the-dream recipe and
  the executor-contract reference.
- docs/cli/index.md: a `ctx dream` row in the Sessions table and the
  `dream:` block in the .ctxrc reference.
- examples.yaml: example blocks for dream and its four subcommands (the
  desc.Example keys existed but had no entries, so --help showed none).
- site/: `zensical build` rebuild so the published site includes the new
  dream CLI page, recipe, and executor-contract reference (built output
  is tracked; bundled with the docs change per convention).

Recipe (docs/recipes/run-the-dream.md) and the executor-contract
reference shipped with the feature commits. A Copilot-CLI skill mirror is
intentionally not added: the sync maintains a curated subset and v1's
reference executor is Claude Code (deferrable follow-up).

Spec: specs/ctx-dream.md
…tifact tests

Close the last ctx-dream v1 test task and harden the schema gate it
exercises.

- ProposalValid now also rejects a proposal with no target or empty
  evidence, not just unknown enums — enforcing the spec's "no evidence
  is not surfaced" provenance rule. New config reason/labels
  (FieldEvidence, FieldTargets, ReasonMissing).
- TestCrashResume: a pass that crashes mid-way persists state only for
  completed sources; the next run reloads that committed state intact
  (atomic write — no torn file) and the discipline clock re-selects
  exactly the unprocessed + changed sources, skipping unchanged ones.
- Corrupted-artifact regression corpus (testdata/corrupted-2605.12978.json,
  modeled on the arXiv 2605.12978 appendix): a well-formed proposal among
  corrupted ones (unknown status, stripped evidence, dropped target).
  TestCorruptedArtifactGate asserts only the provenance-bearing one
  survives; MalformedJSON asserts a structurally corrupt file errors (no
  panic / silent empty); Dedup asserts an already-decided artifact does
  not re-surface.

Spec: specs/ctx-dream.md
Signed-off-by: Jose Alekhinne <jose@ctx.ist>
…lution-hardening

Fix/notify resolution hardening
…cation loop

The session-start hubsync hook returned "" on config-load, dial,
sync-RPC, and write failures with no trace, and conflated a real
sync error with the genuine "nothing new" result. The follower
replication loop returned silently on dial, stream-open, send, and
close-send failures and on every receive error. Operators could not
distinguish a healthy quiet sync from one that never reached the
network.

Wire every silent return through the warn sink with format
constants in config/warn. Behavior is otherwise unchanged: both
functions keep their signatures and never-block contracts.

- Sync now warns per failure site and stays silent only for a
  genuine zero-entry result (the un-conflation from ActiveMemory#100).
- replicateOnce warns per failure site; the receive path stays
  silent for the eof() end-of-stream (the one deliberate deviation
  from the issue's proposed code, which would have warned once per
  replication cycle) and for caller shutdown.
- Two ActiveMemory#100 sub-items (close-defer warn, append warn + keep
  consuming) had already landed; this covers the rest and adds the
  regression tests the issue asked for, including pinning
  keep-consuming-after-append-error and clean-EOF-no-warn.

Closes ActiveMemory#100.

Spec: specs/fix-hub-silent-error-suppression.md
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Omer Kocaoglu <omergk28@gmail.com>
@omergk28 omergk28 requested a review from josealekhine as a code owner June 12, 2026 01:15
@josealekhine

Copy link
Copy Markdown
Member

The fix is correct, and the key insight is right

Some nits below:

The real bug was the conflation of "error" with "nothing new":

// internal/cli/system/core/hubsync/sync.go — before
entries, syncErr := client.Sync(context.Background(), cfg.Types, 0)
if syncErr != nil || len(entries) == 0 {
    return ""
}

The PR un-conflates them so a real error warns while a genuine empty result stays silent:

// after
if syncErr != nil {
    logWarn.Warn(cfgWarn.HubSyncPull, cfg.HubAddr, syncErr)
    return ""
}
if len(entries) == 0 {
    // Genuine empty result: not an error, no warning.
    return ""
}

Every other silent return "" now routes through the logWarn sink, and both functions keep their signatures and non-blocking contract (still return "" everywhere). The replication loop's recv suppression is the right call — silent on io.EOF (normal stream end) and ctx.Err() != nil (shutdown), warn on anything else:

// internal/hub/replicate.go
if recvErr := stream.RecvMsg(msg); recvErr != nil {
    if !eof(recvErr) && ctx.Err() == nil {
        logWarn.Warn(cfgWarn.HubReplicateRecv, masterAddr, recvErr)
    }
    return
}

…and the spec documents this as a deliberate deviation from issue #100's proposed code (which warned on every recv error and would have made clean cycles noisy).

Things I checked that hold up

  • All 9 new warn format/arg arities match. go vet would not catch a mismatch here — Warn(format string, args ...any) isn't a recognized printf-wrapper — so I verified each call by hand (e.g. HubSyncWrite = "hubsync: write %d entries: %v"Warn(..., len(entries), writeErr)).
  • No secret leakage. The token never appears in any warn format; only the hub address + error.
  • Tests are genuinely excellent. Each error path asserts its warning; TestSync_NoWarnOnEmptyResult pins the un-conflation; TestReplicateOnce_CleanReplicationDoesNotWarn pins the EOF suppression (zero warnings on a real round-trip + lastSeq == 2); TestReplicateOnce_KeepsConsumingAfterAppendError pins continue-on-append-failure (chmod-0 dir → exactly 2 append warnings). The control-char ("\x00") trick to force an eager grpc.NewClient failure is a nice touch given the resolver is otherwise lazy.
  • Noise is bounded. The live hook (checkhubsync.Run) is gated by check.DailyThrottled, so Sync and its warnings fire at most once per day, not every session start.
  • Replication path is dormant. replicateOnce is reserved dead code today (var _ = startReplication, "called from Server.Start when a follower peer is configured"), so those warnings won't fire until cluster mode is wired.

Findings (all minor / adjacent — none are blockers)

1. (adjacent, pre-existing) The "must not block the session" contract isn't actually guaranteed

The doc update reaffirms the contract:

// internal/cli/system/core/hubsync/doc.go
// Every error is surfaced as a stderr warning via the warn sink,
// but never propagates: the hook must not block the session start.

But the underlying RPC has no deadline:

// sync.go — context.Background(), no timeout
entries, syncErr := client.Sync(context.Background(), cfg.Types, 0)

// internal/hub/client.go — lazy NewClient, fail-fast (not WaitForReady), no dial timeout
conn, dialErr := grpc.NewClient(addr, grpc.WithTransportCredentials(insecure.NewCredentials()), ...)

A hub that accepts the TCP connection but never responds (hung server / black-hole proxy) makes RecvMsg inside Sync block indefinitely → the session-start hook hangs, contradicting the contract this PR re-documents. Not introduced here (the context.Background() line is unchanged), and the daily throttle limits exposure to once/day — but since the PR draws attention to this exact contract, wrapping the Sync RPC in a context.WithTimeout would make "never block" actually true. This is the one finding with real teeth; worth a follow-up rather than blocking the merge.

2. (forward-looking) Replication warning cadence when wired

replicateOnce is dead code today, but when startReplication is wired (#96 territory), a persistently-down-but-reachable master would warn every ReplicateInterval (5s, per internal/config/hub) via HubReplicateRecv with no dedup/backoff — the EOF+ctx guard only suppresses normal completion and shutdown, not a recurring transport error. Consider warn-once-per-state-transition when that path goes live. Noted now because the warning constants and call sites ship in this PR.

3. (trivial) eof() uses ==, not errors.Is

// internal/hub/eof.go (pre-existing helper)
func eof(err error) bool { return err == stdio.EOF }

Correct for gRPC's unwrapped io.EOF (the clean-replication test confirms it in practice), just marginally less defensive than errors.Is(err, io.EOF) if any layer ever wraps it. Pre-existing, but this PR newly relies on it for the suppression branch.

…ory#114 review)

Review of ActiveMemory#100 surfaced that the re-documented "must not block the
session start" contract was not actually guaranteed: the hubsync pull
RPC ran under context.Background() with no deadline, so a hub that
accepts the connection but never responds (hung server, black-hole
proxy) would hang the session-start hook indefinitely.

- Bound the pull with context.WithTimeout(HubSyncTimeout) — new 10s
  constant in internal/config/hub. An exceeded deadline surfaces via
  the existing HubSyncPull warning and the hook returns "" like any
  other failure; the daily throttle plus next-session retry covers a
  cut-off pull. A test-overridable syncTimeout var (export_test.go
  seam) lets TestSync_WarnsOnHungHub drive a black-hole listener in
  milliseconds instead of the full interval.
- Harden internal/hub/eof.go to errors.Is(err, io.EOF) so the
  replication receive-suppression branch stays correct if any layer
  ever wraps EOF (gRPC delivers it unwrapped today).
- Record the forward-looking finding (dedup/backoff for the
  per-cycle HubReplicateRecv warning once startReplication is wired,
  ActiveMemory#96) as a task rather than guessing now; replicateOnce is dead
  code until then.

Spec: specs/fix-hub-silent-error-suppression.md
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Omer Kocaoglu <omergk28@gmail.com>
@omergk28

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review — addressed in 3cfdfd14:

  • F1 (the one with teeth) — Sync could block on a hung hub. Bounded the pull with context.WithTimeout(context.Background(), HubSyncTimeout) (new 10s constant in internal/config/hub). An exceeded deadline surfaces through the existing HubSyncPull warning and the hook still returns "" like any other failure; the daily throttle + next-session retry cover a cut-off pull. Made syncTimeout a test-overridable var (export_test.go seam) and added TestSync_WarnsOnHungHub, which drives a black-hole TCP listener (accepts the connection, never speaks gRPC) and asserts Sync returns rather than hanging. Sanity check: with the deadline reverted to context.Background(), that test fails at its 5s guard — so it has teeth.
  • F3 — eof() now uses errors.Is(err, io.EOF) instead of ==.
  • F2 (replication warn cadence) — deferred as you suggested. Recorded as a task tied to the Hub status: surface cluster leadership (IsLeader, LeaderAddr) in Status RPC + ctx hub status #96 wiring (with a spec Out-of-Scope note) rather than guessed at now, since replicateOnce is dead code until then.

The deadline (F1) is the only behavior change; the never-block contract the PR re-documents is now actually true.

@josealekhine

Copy link
Copy Markdown
Member

Thanks @omergk28

F1 and F3 landed exactly as discussed, and TestSync_WarnsOnHungHub is the right kind of test (the reverted-deadline sanity check you ran is what makes it credible).

the code fix is right; one more small push and this merges.

Here are some fixes before we can merge this:

M1 — .context/TASKS.md conflicts with main (again). Same story as #117: both sides append to a high-churn memory file, so the PR is mergeable: dirty. Please drop the TASKS.md hunk entirely — I'll record the F2-deferral task on main right after the merge (that's now our standing pattern for contributor PRs: memory files get post-merge follow-up commits, they don't ride in the PR).

M2 — the "next session retries" comments are false. Two new comments justify the 10s deadline with a retry story the code doesn't implement:

// sync.go — "An exceeded deadline surfaces as a warning like any
// other sync failure; the next session retries."

// config/hub/hub.go — "the daily throttle plus next-session retry
// covers a cut-off pull"

But checkhubsync/run.go stamps the throttle marker unconditionally after Sync — and Sync returns "" for success, failure, and empty alike, so the caller can't gate the stamp:

msg := hubsync.Sync(sessionID)
if msg != "" { writeSetup.Nudge(cmd, msg) }
_ = internalIo.TouchFile(markerPath)   // stamps failed pulls too

Retry is next day, not next session. To be clear: I think the unconditional stamp is the right behavior — it's exactly what caps a black-holing hub at one 10s stall per day instead of one per session. Don't change the code; change the two comments ("the next day's first session retries"). While you're in there: Sync's doc comment still says it "returns the count of synced entries and a formatted status message" — it returns only the string.

N1 — keep eof() strict; inline the broadening where it's wanted. errors.Is was my ask (F3), but I only thought about the suppression polarity. eof() has two other callers — client.Sync and client.Listen — where true means clean end of stream. grpc-go today returns bare io.EOF only on OK status (*status.Error has no Unwrap, so this is currently unreachable), but if any future layer wraps a dirty EOF, the broadened helper converts mid-stream truncation into silent partial success at exactly the call sites this PR exists to make honest. Suggest: revert eof() to == and use errors.Is(recvErr, io.EOF) inline in the replicate.go suppression condition — each polarity gets the matching strictness.

N2 — hung-hub test failure path. Cleanup runs LIFO, so if the 5s guard ever fires, the warn-sink restore (registered last) runs before the black-hole conns close — which is what unblocks the leaked Sync goroutine, whose late logWarn.Warn then writes through the process-global unsynchronized sink into whatever test runs next. Cheapest fix: register captureWarnings(t) before blackHoleListener(t), or add a cleanup that drains done. Only matters on the failure path, so genuinely optional.

Things I checked that hold up

  • The SendMsg path is fine: this is a server-streaming RPC (ClientStreams=false), so grpc-go returns nil on transport-side send failure and defers to RecvMsg, which surfaces the true status through your new HubReplicateRecv warn — a revoked token is diagnosable.
  • The ctx.Err() suppression matches the spec's documented "one deliberate deviation" and is correct for the cancellation-only lifetime ctx startReplication will pass.
  • Warn cadence: still deferred to the Hub status: surface cluster leadership (IsLeader, LeaderAddr) in Status RPC + ctx hub status #96 wiring per F2 — agreed.
  • Merged with today's main, go test ./internal/cli/system/core/hubsync/... ./internal/hub/... passes clean.

Additional

This is not direclty part of your PR; but since you are touching relevant files, I'd be glad if you can check them out too.

  • ctx system check-hub-sync turns out to be wired into no shipped hook set — commands.yaml and the hub recipe claim otherwise. Your hardening is correct either way (and the replicate.go/eof.go changes serve reachable paths today), but wiring the hook vs. fixing the docs is a maintainer decision I'll take up in a separate issue, along with a guard that anything claiming "invoked via hooks.json" actually appears there.
  • The 10s ceiling bounds an always-from-zero pull; ctx connection sync already does incremental pulls via persisted LastSequence, so I'll file a follow-up for hubsync to reuse that state — which dissolves the large-hub timeout concern entirely.
  • Client-layer default deadline for the remaining unbounded RPCs (connection sync/publish/register/status, failover, replicateOnce)

Thanks again for all your help 🙏 🌮 .

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.

Hub: silent error suppression in hubsync hook + replication loop

6 participants