Skip to content

feat(integration-mounts): stalled-revision mount-recovery backstop#169

Merged
khaliqgant merged 1 commit into
mainfrom
fix/threads-mount-bare-suffix-stopgap
Jun 8, 2026
Merged

feat(integration-mounts): stalled-revision mount-recovery backstop#169
khaliqgant merged 1 commit into
mainfrom
fix/threads-mount-bare-suffix-stopgap

Conversation

@khaliqgant

@khaliqgant khaliqgant commented Jun 8, 2026

Copy link
Copy Markdown
Member

What

A defense-in-depth detector in the mount health poll that catches a mount which is cycling status:ready with no error yet has silently stopped materializing new revisions — and force-restarts it (with clearState) so the next full pull re-pulls the missed files.

This is the general client-side backstop for the failure class behind the Slack threads read-down outage (cloud emitted bare channel-id event paths that the mount's literal-prefix isUnderRemoteRoot filter silently dropped, so the mount kept cycling healthy while never tracking new replies). The durable fix for that specific bug shipped server-side in cloud#2010 and is live-verified in prod (a fresh post-deploy reply materialized via incremental sync within seconds, no Pear restart). This PR ensures any future silent incremental-sync drop self-recovers instead of staying invisible until the ~100min periodic full pull.

Scope note: an earlier revision of this PR also carried a threads-only RELAYFILE_MOUNT_WEBSOCKET=false staleness-floor stopgap (#1). It was dropped after cloud#2010 restored correct incremental read-down with websocket on — there's no reason to give up ws immediacy. This PR is now the detector only.

How

checkMountHealth previously force-restarted only on auth failure or a deadline/sync wedge — nothing detected "cycling successfully but not pulling new revisions." The new check (readStalledInjectedRevisions + a branch in the per-handle loop) fires queueForcedRestart(remotePath, 'stalled events', { clearState: true }) iff:

  • the event-bridge injected a revision under this mount (logged as "injecting" in integration-events.log) whose path is absent from the mount's own state.json files after a 15min wall-clock grace, AND
  • the mount has successfully reconciled since the revision arrived (lastSuccessfulReconcileAt > injectedAt) — proving it's alive and had a chance to pull it, so absence is a real drop, not an in-flight/slow pull or a down/booting mount.

Reuses the existing 60s restart throttle + handledHealthErrorKeys dedup (one drop = one restart, no storm). General across mounts — only mounts with "injecting" entries under their remoteRoot can match, so discovery/* etc. never false-fire.

Why the grace is minutes, not seconds

Under normal operation a received revision lands within one reconcile (incremental in seconds; a ws→poll fallback within ~5min). 15min clears that normal latency with wide margin and is well under the ~100min periodic full-pull self-heal, so the detector recovers a genuine drop far sooner than a scheduled full pull would, without thrashing on a merely in-flight pull. Wall-clock (not cycle-count) stays correct if interval/transport knobs are retuned later.

Correctness anchors (verified against the v0.8.19 daemon source)

  • state.files is map[string]trackedFile keyed by the full normalized suffixed remote path, so syncedPaths.has(injectedPath) is full-suffixed vs full-suffixed — apples-to-apples (if it were remoteRoot-relative this would storm; it isn't).
  • lastSuccessfulReconcileAt is in the daemon's public state.json, UTC RFC3339Nano (directly ISO-comparable), omitempty → a never-reconciled mount omits it → detector skips it.
  • INTEGRATION_EVENT_LOG_PATH is duplicated in integration-mounts.ts to avoid a mounts → bridge import cycle.

Tests

npx vitest run src/main/integration-mounts.test.ts31 passed (7 new for the detector: fires when aged+reconciled+absent; no-op within grace, when already tracked, when not reconciled since receipt, when never reconciled, for other mount roots, and when the events log is unreadable). tsc --noEmit clean (exit 0).

Rollout

Draft / no rush — not blocking (cloud#2010 already fixed the active bug). Lands on a normal restart cycle whenever convenient; the detector runs in-process so it takes effect on the next app start. Do not merge/restart without operator greenlight.

🤖 Generated with Claude Code

@codeant-ai

codeant-ai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Your free trial PR review limit of 300 PRs has been reached. Please upgrade your plan to continue using CodeAnt AI.

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@khaliqgant, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 58 minutes and 34 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2bf40ae1-126e-48a4-bcdd-6b01bf1e046b

📥 Commits

Reviewing files that changed from the base of the PR and between 95c1f47 and ce2d065.

📒 Files selected for processing (2)
  • src/main/integration-mounts.test.ts
  • src/main/integration-mounts.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/threads-mount-bare-suffix-stopgap

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 and usage tips.

@khaliqgant khaliqgant marked this pull request as draft June 8, 2026 13:09

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces a mechanism to detect and recover from stalled injected revisions (silently dropped incremental events) by scanning the integration events log and forcing a mount restart with a fresh full pull when necessary. It also disables websocket transport for Slack /threads subtrees to enforce more frequent polling. The review feedback highlights three key areas for improvement: optimizing performance by caching the parsed log lines to avoid redundant disk reads across multiple active mounts, narrowing the Slack threads detection to prevent false positives on other integration paths, and addressing potential unbounded growth of the integration events log file.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +739 to +757
let logText: string
try {
logText = await readFile(INTEGRATION_EVENT_LOG_PATH, 'utf8')
} catch {
return null
}

const syncedPaths = new Set(Object.keys(asRecord(state.files) ?? {}))
// omitempty in the daemon's publicState: a never-reconciled mount omits this,
// so lastReconcileMs stays null and every candidate is skipped below.
const lastReconcileMs = parseTimestamp(
typeof state.lastSuccessfulReconcileAt === 'string' ? state.lastSuccessfulReconcileAt : null
)
const prefix = `${remotePath}/`
const now = Date.now()
const missing: Array<{ path: string; injectedAt: string }> = []
const seen = new Set<string>()

for (const line of logText.trim().split(/\r?\n/u).slice(-STALLED_REVISION_LOG_SCAN_LINES)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Performance Bottleneck: Repeated File Reads and String Splitting

In checkMountHealth, readStalledInjectedRevisions is called sequentially for every active mount. Currently, each invocation reads the entire log file from disk and splits the entire string into lines. If there are $N$ active mounts, this results in $N$ disk reads and $N$ full string splits every 45 seconds. As the log file grows, this will cause significant CPU spikes, event loop blockage, and high memory usage.

We can optimize this by caching the parsed lines for a short duration (e.g., 5 seconds) using static properties on the function itself. This ensures the file is read and parsed at most once per health check cycle, while automatically bypassing the cache during tests to keep them isolated.

  const isTest = typeof process !== 'undefined' && (process.env.NODE_ENV === 'test' || process.env.VITEST === 'true')
  const now = Date.now()
  let lines: string[]

  const self = readStalledInjectedRevisions as any
  if (!isTest && self.cachedLines && now - self.cachedLinesTimestamp < 5000) {
    lines = self.cachedLines
  } else {
    try {
      const logText = await readFile(INTEGRATION_EVENT_LOG_PATH, 'utf8')
      lines = logText.trim().split(/\\r?\\n/u).slice(-STALLED_REVISION_LOG_SCAN_LINES)
      if (!isTest) {
        self.cachedLines = lines
        self.cachedLinesTimestamp = now
      }
    } catch {
      return null
    }
  }

  const syncedPaths = new Set(Object.keys(asRecord(state.files) ?? {}))
  const lastReconcileMs = parseTimestamp(
    typeof state.lastSuccessfulReconcileAt === 'string' ? state.lastSuccessfulReconcileAt : null
  )
  const prefix = `${remotePath}/`
  const missing: Array<{ path: string; injectedAt: string }> = []
  const seen = new Set<string>()

  for (const line of lines) {

Comment thread src/main/integration-mounts.ts Outdated
// cloud fs/events emitting the suffixed segment). The binary parses this via
// strconv.ParseBool, so 'false' definitively disables it and the var must be
// PASSED (omitting it falls back to true).
const isAliasSubtree = spec.remotePath.endsWith('/threads')

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Overly Broad Path Match for Alias Subtree

The check spec.remotePath.endsWith('/threads') is used to identify Slack threads. However, this could accidentally match paths from other integration providers that happen to end with /threads (e.g., /linear/issues/threads or /github/threads), disabling their websocket transport unnecessarily.

To prevent false positives, we should restrict this check to Slack paths by ensuring the path starts with /slack/.

Suggested change
const isAliasSubtree = spec.remotePath.endsWith('/threads')
const isAliasSubtree = spec.remotePath.startsWith('/slack/') && spec.remotePath.endsWith('/threads')

const STALLED_REVISION_LOG_SCAN_LINES = 500
// Mirrors integration-event-bridge.ts INTEGRATION_EVENT_LOG_PATH. Duplicated to
// avoid a mounts->bridge import cycle; hoist to a shared module if it grows.
const INTEGRATION_EVENT_LOG_PATH = join(homedir(), '.agentworkforce', 'pear', 'integration-events.log')

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Unbounded Log File Growth

The log file integration-events.log is appended to indefinitely without any rotation or truncation mechanism. Over time, this file can grow to hundreds of megabytes or gigabytes, which will eventually cause readFile to fail or exhaust system memory.

Consider implementing a log rotation or truncation strategy (e.g., capping the file size or rotating it periodically) in the event bridge layer to ensure long-term stability.

agent-relay-code Bot added a commit that referenced this pull request Jun 8, 2026
@agent-relay-code

Copy link
Copy Markdown
Contributor

Fixed one validated PR issue: the websocket stopgap now applies only to Slack channel/DM/user thread alias roots, instead of every mount ending in /threads such as Gmail. See integration-mounts.ts and helper at integration-mounts.ts.

Added regression coverage that executes the generated launcher env for both Slack and Gmail cases: integration-mounts.test.ts.

Addressed comments

  • No bot or reviewer comments were present in the local PR context files (.workforce/pr.diff, .workforce/changed-files.txt, .workforce/context.json), so there were no external review threads to validate or resolve.

Verification run:

  • npx vitest run src/main/integration-mounts.test.ts passed: 33 tests.
  • npm test passed: 104 tests.
  • npx tsc -p tsconfig.node.json --noEmit was attempted, but this checkout’s raw TypeScript check fails broadly on pre-existing unresolved dependency/config issues across unrelated files, so it was not usable as PR-specific validation.

@agent-relay-code

Copy link
Copy Markdown
Contributor

pr-reviewer applied fixes — committed and pushed e2a46b4 to this PR. The notes below describe what changed.

Fixed one validated PR issue: the websocket stopgap now applies only to Slack channel/DM/user thread alias roots, instead of every mount ending in /threads such as Gmail. See integration-mounts.ts and helper at integration-mounts.ts.

Added regression coverage that executes the generated launcher env for both Slack and Gmail cases: integration-mounts.test.ts.

Addressed comments

  • No bot or reviewer comments were present in the local PR context files (.workforce/pr.diff, .workforce/changed-files.txt, .workforce/context.json), so there were no external review threads to validate or resolve.

Verification run:

  • npx vitest run src/main/integration-mounts.test.ts passed: 33 tests.
  • npm test passed: 104 tests.
  • npx tsc -p tsconfig.node.json --noEmit was attempted, but this checkout’s raw TypeScript check fails broadly on pre-existing unresolved dependency/config issues across unrelated files, so it was not usable as PR-specific validation.

@cubic-dev-ai cubic-dev-ai 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.

2 issues found across 2 files

You’re at about 98% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/main/integration-mounts.ts
Comment thread src/main/integration-mounts.ts
agent-relay-code Bot added a commit that referenced this pull request Jun 8, 2026
@agent-relay-code

Copy link
Copy Markdown
Contributor

pr-reviewer applied fixes — committed and pushed 5ab9a79 to this PR. The notes below describe what changed.

Implemented and verified fixes for the validated review findings.

Changes made:

  • Cached stalled-revision log parsing for production health polls in src/main/integration-mounts.ts:794.
  • Scanned newest injected log entries first so replayed paths dedupe to the latest observation in src/main/integration-mounts.ts:764.
  • Made injecting event-log entries durable even when verbose debug is off in src/main/integration-event-bridge.ts:1522.
  • Added log size trimming before append in src/main/integration-event-bridge.ts:1583.
  • Added replay regression coverage in src/main/integration-mounts.test.ts:941.

Addressed comments

  • codeant-ai[bot]: trial-limit notice only; no code finding to fix.
  • coderabbitai[bot]: review skipped because PR was draft; no code finding to fix.
  • gemini-code-assist[bot] review summary: performance caching fixed in src/main/integration-mounts.ts:794; Slack /threads false positive was already handled in current checkout at src/main/integration-mounts.ts:642; unbounded log growth fixed in src/main/integration-event-bridge.ts:1583.
  • gemini-code-assist[bot]: repeated file reads/string splitting fixed in src/main/integration-mounts.ts:794.
  • gemini-code-assist[bot]: unbounded log growth fixed in src/main/integration-event-bridge.ts:62 and src/main/integration-event-bridge.ts:1583.
  • agent-relay-code[bot]: prior outcome comment about Slack/Gmail websocket scoping is stale; current checkout already handles it at src/main/integration-mounts.ts:642 with tests at src/main/integration-mounts.test.ts:298.
  • agent-relay-code[bot]: duplicate prior push-outcome comment is stale for the same reason; current checkout already handles it at src/main/integration-mounts.ts:642.
  • cubic-dev-ai[bot]: top-level review raised debug-only logging and oldest-first dedupe; fixed in src/main/integration-event-bridge.ts:1522 and src/main/integration-mounts.ts:764.
  • cubic-dev-ai[bot]: oldest-first stalled scan fixed in src/main/integration-mounts.ts:764, with regression at src/main/integration-mounts.test.ts:941.
  • cubic-dev-ai[bot]: debug-only stalled detector dependency fixed in src/main/integration-event-bridge.ts:1522.

Verification run:

  • npx vitest run src/main/integration-mounts.test.ts passed: 34 tests.
  • npm test passed: 104 tests.
  • Production-style smoke script confirmed durable injecting log entries are written with debug off.
  • npm run verify:mcp-resources-drift passed.
  • npx vitest run src/main/broker.test.ts passed: 59 tests.
  • npm run build passed.

I’m not printing READY because I cannot confirm final GitHub mergeability and required check completion after these local edits from inside this run.

agent-relay-code Bot added a commit that referenced this pull request Jun 8, 2026
@agent-relay-code

Copy link
Copy Markdown
Contributor

Implemented one validated fix outside the original changed-file set: the new stalled-mount detector depended on integration-events.log, but injection entries were only written when verbose debug logging was enabled. I added a narrow non-debug breadcrumb writer for Slack thread-subtree injections in integration-event-bridge.ts, and switched the injection path to use it at integration-event-bridge.ts. Added regression coverage at integration-event-bridge.test.ts.

Addressed comments

  • No bot or reviewer comment artifacts were present in .workforce; I found only pr.diff, changed-files.txt, and context.json, so there were no external review comments to validate or resolve.

Local validation passed:

  • npm run verify:mcp-resources-drift
  • npm test
  • npx vitest run src/main/integration-mounts.test.ts
  • npx vitest run src/main/broker.test.ts
  • node --experimental-strip-types --no-warnings --test src/main/__tests__/integration-event-bridge.test.ts
  • npm run build

@agent-relay-code

Copy link
Copy Markdown
Contributor

pr-reviewer applied fixes — committed and pushed c8ff053 to this PR. The notes below describe what changed.

Implemented one validated fix outside the original changed-file set: the new stalled-mount detector depended on integration-events.log, but injection entries were only written when verbose debug logging was enabled. I added a narrow non-debug breadcrumb writer for Slack thread-subtree injections in integration-event-bridge.ts, and switched the injection path to use it at integration-event-bridge.ts. Added regression coverage at integration-event-bridge.test.ts.

Addressed comments

  • No bot or reviewer comment artifacts were present in .workforce; I found only pr.diff, changed-files.txt, and context.json, so there were no external review comments to validate or resolve.

Local validation passed:

  • npm run verify:mcp-resources-drift
  • npm test
  • npx vitest run src/main/integration-mounts.test.ts
  • npx vitest run src/main/broker.test.ts
  • node --experimental-strip-types --no-warnings --test src/main/__tests__/integration-event-bridge.test.ts
  • npm run build

agent-relay-code Bot added a commit that referenced this pull request Jun 8, 2026
@agent-relay-code

Copy link
Copy Markdown
Contributor

pr-reviewer applied fixes — committed and pushed f4563b5 to this PR. The notes below describe what changed.

Fixed a production duplicate-write bug in integration event breadcrumbs: non-debug Slack thread injections now persist through the breadcrumb path only, instead of also being written by generic debug logging. See integration-event-bridge.ts.

Also tightened the breadcrumb test to wait for the async log write and assert exactly one persisted entry. See integration-event-bridge.test.ts and integration-event-bridge.test.ts.

Addressed comments

  • No bot or reviewer comments were supplied in .workforce/context.json or the other provided .workforce files, so there were no external review threads to validate.

Verification run:

  • node --experimental-strip-types --no-warnings --test src/main/__tests__/integration-event-bridge.test.ts passed.
  • npx vitest run src/main/integration-mounts.test.ts passed.
  • npm test passed.
  • npm run build passed.
  • npx tsc --noEmit -p tsconfig.node.json still fails on existing repo-wide TypeScript/config issues unrelated to this PR, including target/lib errors and pre-existing type mismatches across many files.

@agent-relay-code

Copy link
Copy Markdown
Contributor

ℹ️ pr-reviewer: review only — no file changes were applied to the PR (nothing to commit after review). The notes below are advisory and were not pushed.

Reviewed PR #169 against .workforce/pr.diff, changed files, and current checkout. I did not find a reproducible defect in the PR changes, so I left the working tree code unchanged.

Addressed comments

  • No bot or reviewer comments were present in .workforce/context.json or other .workforce artifacts, so there were no external review threads to validate or fix.

Local validation run:

  • npm test -- src/main/__tests__/integration-event-bridge.test.ts passed
  • npx vitest run src/main/integration-mounts.test.ts passed
  • npm run build passed after npm install

I am not printing READY because I cannot verify GitHub CI status or mergeability from this sandbox without using gh/GitHub access.

Adds a defense-in-depth detector to the mount health poll that catches a
mount which is cycling status:ready with no error yet has silently stopped
materializing new revisions — the failure mode behind the Slack threads
read-down outage (cloud emitted bare channel-id event paths that the mount's
literal-prefix isUnderRemoteRoot filter dropped). The durable fix for that
specific bug shipped server-side in cloud#2010 (live-verified in prod); this
is the general client-side backstop so any future silent incremental-sync
drop self-recovers instead of staying invisible until the ~100min full pull.

Detector (readStalledInjectedRevisions + a check in checkMountHealth, which
previously restarted ONLY on auth failure or deadline wedge): if the
event-bridge injected a revision under a mount (logged as "injecting" in
integration-events.log) that the mount's own state.json still doesn't list
after a 15min wall-clock grace AND the mount has successfully reconciled
since the revision arrived (lastSuccessfulReconcileAt > injectedAt), force a
queueForcedRestart(..., { clearState: true }) so the next full pull re-pulls
it. Reuses the existing 60s restart throttle + handledHealthErrorKeys dedup,
so one drop = one restart. General across mounts; only mounts with injecting
entries under their remoteRoot can match.

Correctness anchors (verified against the v0.8.19 daemon source): state.files
is keyed by the full normalized suffixed remote path, so the comparison is
apples-to-apples; lastSuccessfulReconcileAt is UTC RFC3339Nano + omitempty,
so a never-reconciled mount is skipped. Grace is wall-clock (not cycle-count)
to survive interval/transport retuning. Adds 7 unit tests for the detector.

(Originally also carried a threads-only RELAYFILE_MOUNT_WEBSOCKET=false
staleness-floor stopgap; dropped after cloud#2010 restored correct
incremental read-down with websocket on.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@khaliqgant khaliqgant force-pushed the fix/threads-mount-bare-suffix-stopgap branch from f4563b5 to ce2d065 Compare June 8, 2026 15:01
@khaliqgant khaliqgant changed the title fix(integration-mounts): threads stopgap for bare-vs-suffixed event drop feat(integration-mounts): stalled-revision mount-recovery backstop Jun 8, 2026
@khaliqgant khaliqgant marked this pull request as ready for review June 8, 2026 15:35
@khaliqgant khaliqgant merged commit 4f3a28e into main Jun 8, 2026
2 of 3 checks passed
@khaliqgant khaliqgant deleted the fix/threads-mount-bare-suffix-stopgap branch June 8, 2026 15:35

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 2 files

You’re at about 99% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/main/integration-mounts.ts">

<violation number="1" location="src/main/integration-mounts.ts:362">
P1: Stalled-event handling marks the issue as handled even when restart is throttled, so recovery may never be retried.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment on lines +362 to +371
this.handledHealthErrorKeys.set(remotePath, healthErrorKey)
if (queued) {
console.warn(
`[integration-mounts] Mount stalled (injected revisions never materialized) for ${remotePath}; restarting with fresh full pull`,
{
missing: stalled.missingCount,
oldestInjectedAt: stalled.oldestInjectedAt,
examples: stalled.examples
}
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: Stalled-event handling marks the issue as handled even when restart is throttled, so recovery may never be retried.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/main/integration-mounts.ts, line 362:

<comment>Stalled-event handling marks the issue as handled even when restart is throttled, so recovery may never be retried.</comment>

<file context>
@@ -328,6 +346,32 @@ export class IntegrationMountManager {
+          const healthErrorKey = ['stalled-events', stalled.oldestInjectedAt, stalled.missingCount].join('|')
+          if (this.handledHealthErrorKeys.get(remotePath) === healthErrorKey) continue
+          const queued = this.queueForcedRestart(remotePath, 'stalled events', { clearState: true })
+          this.handledHealthErrorKeys.set(remotePath, healthErrorKey)
+          if (queued) {
+            console.warn(
</file context>
Suggested change
this.handledHealthErrorKeys.set(remotePath, healthErrorKey)
if (queued) {
console.warn(
`[integration-mounts] Mount stalled (injected revisions never materialized) for ${remotePath}; restarting with fresh full pull`,
{
missing: stalled.missingCount,
oldestInjectedAt: stalled.oldestInjectedAt,
examples: stalled.examples
}
)
if (queued) {
this.handledHealthErrorKeys.set(remotePath, healthErrorKey)
console.warn(
`[integration-mounts] Mount stalled (injected revisions never materialized) for ${remotePath}; restarting with fresh full pull`,
{
missing: stalled.missingCount,
oldestInjectedAt: stalled.oldestInjectedAt,
examples: stalled.examples
}
)
}

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