- name
- stateful-message-processing
- description
- Review, implement, or test openclaw-weixin changes involving persistent or account-scoped state, context or dedupe keys, claims, locks, monitor admission lanes, retries, abort handling, migrations, or resource cleanup.
# Stateful message processing
Use this playbook with the repository contracts in `AGENTS.md`. Preserve
account isolation, legacy state, polling progress, the approval lane, abort
behavior, and privacy while changing stateful code.
## Define states and ownership first
Before editing, write down the observable regression and complete this model for
each acquired claim, lock, file, or database handle:
| State or resource | Owner | Success transition | Failure/abort transition | Waiter behavior | Lane effect |
| --- | --- | --- | --- | --- | --- |
| Example: claimed inbound key | Processing delivery | Commit after successful handling | Release on every error or abort | Retry only after release | Unrelated messages still advance |
Keep states with different retry behavior distinct. In particular, do not reduce
`claimed`, `inflight`, `committed`, `released`, and `aborted` to a duplicate
boolean.
Ownership starts when the resource is acquired. Put the first subsequent
operation that can throw inside the complete commit/release or close/finally
boundary, including logging, status updates, config lookup, payload inspection,
and preprocessing.
## Design collision-safe identity
- Prefer stable transport message IDs, then canonical item-level IDs.
- Use a canonical digest only when all inputs are message-specific and stable.
- Include account scope in persistent namespaces; never introduce a global
fallback.
- Prove two distinct valid messages or accounts remain distinct.
- If no collision-safe message identity exists, skip dedupe instead of using a
sender-only or empty-body key.
- Treat full keys and fingerprints as sensitive when they embed account, sender,
message, token, body, or CDN data. Log only a non-sensitive identity kind and
outcome.
## Preserve progress and retry
Test outcomes rather than prescribing one implementation:
- An accepted ordinary turn must not stop polling or block a later unrelated
message.
- Plugin approval work must retain its independent lane.
- An inflight replay must not hold an admission lane while its owner runs.
- Owner commit drops the replay exactly once.
- Owner release or failure leaves one delivery able to retry.
- Abort stops the in-flight long poll and does not strand owned state.
If a fix for one outcome changes scheduling, rerun the complete state matrix.
Do not patch review findings one transition at a time.
## Use deterministic harnesses
- Reuse `createDeferred` from `test/helpers/deferred.ts` to control ownership and
ordering explicitly.
- Reuse `makeTextMessage` and `createChannelRuntimeHarness` where applicable.
- Prefer fake timers over sleeps.
- Keep tests safe under Vitest's default parallelism.
- Do not call the live backend, perform QR login, or use developer state.
Cover every applicable scenario:
| Scenario | Required observation |
| --- | --- |
| Owner commits | A replay is dropped only after commit |
| Owner releases | A waiting or later delivery can retry exactly once |
| Owner still runs, unrelated message arrives | The unrelated message advances before the owner finishes |
| Preprocessing throws after acquisition | Owned state is released |
| Abort occurs | Polling exits and no claim or lock remains stranded |
| Two messages lack outer transport IDs | Item identity keeps them distinct, or dedupe is skipped |
| Two accounts use the same message identity | Persistent state remains account-scoped |
## Test persistence and cleanup
- Isolate both `OPENCLAW_STATE_DIR` and the higher-precedence
`OPENCLAW_OAUTH_DIR`; clear the latter or point it to its own temporary
directory.
- Cover write/read, account isolation, case normalization, supported legacy
formats, and module or process restart.
- Close database, file, and lock handles before deleting temporary directories.
- Restore environment variables, timers, modules, and mocks.
- Do not swallow cleanup failures or turn them into success-shaped fallbacks.
## Completion evidence
- The focused test fails for the original state or ordering reason without the
fix.
- Every affected state transition and error exit has an observable assertion.
- Logs omit raw identifiers and full keys.
- `npm run check` passes after the focused suites.
GitHub에서 보기