---
title: Frequently asked questions (*FAQ*)
description: >
  Common questions about CocoIndex behavior — how change detection propagates
  through logic vs inputs, runtime invocation tracking, hidden dependencies,
  and crash recovery vs cross-target atomicity for target states.
---
## Change detection

### Why do logic changes propagate transitively but input changes don't?

In the call chain `foo(a)` → `bar(b)`:

- **Logic changes propagate**: if `bar`'s logic changes (code, `deps`, `version`), the output of `foo(a)` could be different too, so `foo`'s memo must be invalidated.
- **Input changes don't propagate**: `b` is the result of applying part of `foo`'s logic to `a`. As long as `foo`'s logic and `a` are unchanged, `b` won't change — there's nothing to propagate.

### How does logic change propagation work?

Logic changes propagate based on **runtime invocations**, not static call graphs. Two consequences:

- **Unannotated functions don't break the chain.** If `f1()` → `f2()` → `f3()`, and `f1` and `f3` are decorated with `@coco.fn` but `f2` is not, a logic change in `f3` still invalidates `f1`'s memo.
- **Conditional calls are tracked precisely.** If `f1()` calls `f2()` only in one branch, then invocations of `f1()` that didn't call `f2()` are not invalidated when `f2`'s logic changes — only invocations that actually called `f2()` are affected.

### What about hidden dependencies like global variables or files?

Like any memoization system (e.g., `@functools.cache`), CocoIndex's change detection assumes functions depend only on their declared inputs. If a function reads a global variable, a file, or external state not passed through arguments, changes to those won't be detected automatically.

CocoIndex provides mechanisms to capture some of these dependencies:

- **[`deps`](./programming_guide/function#deps)** — declares module-level values (like a prompt string or model name) as part of the function's logic. Changes to these values invalidate dependent memos, just like any other logic change. Note: `deps` is snapshotted once at decoration time.
- **[`use_context()`](./programming_guide/context)** — retrieves shared resources via `ContextKey`. With [`detect_change=True`](./programming_guide/context#change-detection), changes to the provided value invalidate dependent memos.

For per-call values that change at runtime, pass them as regular function arguments instead.

## Target states and syncing

### What happens if my pipeline crashes mid-update?

CocoIndex's internal state is always consistent — even after a crash or `kill -9`. On the next `app.update()`, CocoIndex automatically recovers: it computes the current desired state and reconciles against all possible previous states, converging the target to the correct state. No manual cleanup is needed. See [Error Handling — Interrupted updates and recovery](./advanced_topics/exception_handlers#interrupted-updates-and-recovery) for details.

### Are target state writes transactional across targets?

Not across targets. When a processing component finishes, CocoIndex sends all its target state changes to each target backend as a unit — all writes happen after processing completes, never partially during execution. Each target backend applies its batch atomically when supported (e.g., within a database transaction). But changes across *different* target backends (e.g., Postgres and local files) are not transactional with each other. See [How target states sync](./programming_guide/processing_component#how-target-states-sync) for details.
