Skip to content

Regression: #2284 fix for auto-memory-hook workingDir reverted by dogfood-helper sync (a5f86ad0); still broken in 3.41.2 #3286

Closed

Description

@patrickitalo

Summary

The fix for #2284 (auto-memory-hook.mjs hardcodes workingDir: PROJECT_ROOT) landed in
facec4cc on 2026-06-04, was never propagated to the package-distributed copy, and was
then reverted on the root copy by a5f86ad0 on 2026-07-04. Both copies on main
today, and the published 3.41.2 tarball, carry the original buggy line again.

#2284 is closed as COMPLETED, so the regression is invisible.

Separately: the fix addressed only workingDir. The underlying PROJECT_ROOT definition
it derives from was never touched, so DATA_DIR / STORE_PATH stay wrong even with the
original fix applied. Details in "Scope" below.

Regression trail

Date Commit Effect
2026-06-04 facec4cc Fixes #2284 on .claude/helpers/auto-memory-hook.mjs: -workingDir: PROJECT_ROOT / +workingDir: process.env.CLAUDE_FLOW_CWD || process.cwd() (two sites: doImport, doSync).
2026-06-15 e7783f2f "reconcile diverged CLI-package helper copies (FIX 2/4/5)" propagates FIX 2/3/4/5 to v3/@claude-flow/cli/.claude/helpers/, but not this one. The distributed copy still has the bug.
2026-07-04 a5f86ad0 "chore: sync repo dogfood helpers to 3.23.0" copies the installed package's helpers over the repo's root copy. Since the package copy never received the fix, this reverts it: -workingDir: process.env.CLAUDE_FLOW_CWD || process.cwd() / +workingDir: PROJECT_ROOT, twice.

Verification on current main:

.claude/helpers/auto-memory-hook.mjs:20                 const PROJECT_ROOT = join(__dirname, '../..');
.claude/helpers/auto-memory-hook.mjs:275                    workingDir: PROJECT_ROOT,
.claude/helpers/auto-memory-hook.mjs:335                    workingDir: PROJECT_ROOT,
v3/@claude-flow/cli/.claude/helpers/auto-memory-hook.mjs:20/275/335   (identical)

npm pack @claude-flow/cli@3.41.2 ships the same three lines. The three affected helper
files are byte-identical between 3.41.1 and 3.41.2, so upgrading does not help.

Scope: the fix was too narrow to begin with

workingDir is one of three consumers of the same bad root:

// auto-memory-hook.mjs:20
const PROJECT_ROOT = join(__dirname, '../..');
const DATA_DIR   = join(PROJECT_ROOT, '.claude-flow', 'data');   // :21
const STORE_PATH = join(DATA_DIR, 'auto-memory-store.json');     // :22

Restoring only the workingDir lines fixes where the bridge looks for memory files, but
the backend store is still written under the wrong root. Meanwhile intelligence.cjs
resolves its own root correctly:

// intelligence.cjs:20-36
function resolveProjectRoot(startDir) {
  if (process.env.CLAUDE_PROJECT_DIR) return path.resolve(process.env.CLAUDE_PROJECT_DIR);
  // ...walk up looking for .git / .claude-flow
}
const PROJECT_ROOT = resolveProjectRoot(process.cwd());
const STORE_PATH   = path.join(PROJECT_ROOT, '.claude-flow', 'data', 'auto-memory-store.json');

So the writer and the reader disagree on the store path. intelligence.consolidate()
returns {"entries":0,"edges":0,"message":"No store to consolidate"} forever, and
intelligence.init() returns {"nodes":0,...,"message":"No memory entries to index"} —
which the hook-handler prints only when nodes > 0 (hook-handler.cjs:510), so the user
sees nothing at all. The hooks appear dead while exiting 0.

metrics-db.mjs:15 and learning-service.mjs:29 have the identical join(__dirname, '../..')
definition.

Reproduction

Trigger condition is the $HOME fallback in the generated settings.json hooks:

sh -c 'D="${CLAUDE_PROJECT_DIR:-.}"; [ -f "$D/.claude/helpers/hook-handler.cjs" ] || D="${HOME}"; exec node "$D/.claude/helpers/hook-handler.cjs" route'

Any project without a local .claude/helpers/ takes D="$HOME", so the helper runs from
~/.claude/helpers/ and join(__dirname, '../..') resolves to $HOME rather than the
project. (#2284 reported the same shape via the plugin marketplace path; the $HOME
fallback is a second route to it.)

cd /path/to/a/project-without-local-helpers
CLAUDE_PROJECT_DIR=$PWD node ~/.claude/helpers/auto-memory-hook.mjs import
# [AutoMemory] ✓ Imported 0 entries (0 skipped)

ls ~/.claude-flow/data/auto-memory-store.json     # exists, contains []
ls ./.claude-flow/data/auto-memory-store.json     # ENOENT — what intelligence.cjs reads

Suggested fix

Fix the root rather than the three call sites, in both copies of all three helpers:

const PROJECT_ROOT = process.env.CLAUDE_PROJECT_DIR || join(__dirname, '../..');

Preferring CLAUDE_PROJECT_DIR (rather than re-applying CLAUDE_FLOW_CWD || process.cwd())
makes the .mjs helpers agree with intelligence.cjs's existing resolveProjectRoot()
precedence by construction, which is the actual invariant being violated. Verified locally:
Imported 2 entries → init: {"nodes":2,...} → consolidate: {"entries":2,...}, with all
artifacts landing under <project>/.claude-flow/data/.

Two process points worth considering, since this is the second time this file regresses:

  1. a5f86ad0's dogfood-sync direction (installed package → repo) silently overwrites repo
    fixes that have not yet shipped. A guard or a sync in the other direction would have
    caught it.
  2. The two committed copies of these helpers keep diverging (e7783f2f exists solely to
    reconcile them). A generated-from-single-source arrangement, or a CI check that diffs
    .claude/helpers/ against v3/@claude-flow/cli/.claude/helpers/, would close the class.

Environment

  • ruflo 3.41.1 (Homebrew global), @claude-flow/cli 3.41.1; verified identical in 3.41.2
  • macOS (Darwin 25.6.0), node via Homebrew
  • Project with no local .claude/helpers/ and no node_modules (Go project), so both the
    $HOME hook fallback and the @claude-flow/memory resolution path are exercised

Related

Activity

  1. patrickitalo commented on Sep 11, 2026

    @patrickitalo
    Author

    Additional finding while patching this locally: affected users have no durable workaround, because the version-stamped auto-refresh reverts the fix on the one file that matters.

    Observed live

    Patched ~/.claude/helpers/auto-memory-hook.mjs at 20:59. By 21:03 the file was byte-identical to the unpatched package copy again and .helpers-version had been stamped 3.41.2. No ruflo command was run knowingly — helper-refresh.js is imported on every CLI startup, so any incidental CLI invocation re-copies the helpers whenever the stamp is stale.

    Why only one of the three files regresses

    CRITICAL_HELPERS in dist/src/init/helper-refresh.js:

    export const CRITICAL_HELPERS = [
        'auto-memory-hook.mjs',
        'hook-handler.cjs',
        'intelligence.cjs',
        'statusline.cjs',
    ];

    The same const PROJECT_ROOT = join(__dirname, '../..'); also lives in metrics-db.mjs:15 and learning-service.mjs:29. Those are not on the list, so a local patch there is permanent — while the patch on auto-memory-hook.mjs, the file this issue is actually about, is not.

    Why patching the package source is not an option either

    The natural workaround — patch the copy the refresh reads from, so it propagates the fix instead of reverting it — is closed off by the integrity gate, which hashes the source, not the destination:

    const expected = trusted.files[name];
    if (!expected || sha256Hex(fs.readFileSync(sp)) !== expected) {
        return { wrote: false, blocked: `integrity check failed for ${name} — refusing to install` };
    }

    A patched source fails its signed hash and the gate fails closed for all of CRITICAL_HELPERS — so that workaround would silently freeze legitimate updates to hook-handler.cjs, intelligence.cjs and statusline.cjs as well. Correct behaviour for a signing gate; it just means there is no clean local mitigation.

    So the options a user actually has are: re-apply after every version bump, or give the project a local .claude/helpers/ so join(__dirname, '../..') happens to resolve correctly and the $HOME fallback is never taken. Across 26 ruflo-initialised projects here, that split is exactly what the data shows — every project with a local helpers dir has a populated auto-memory-store.json, and nearly every project on the $HOME fallback has none. Restoring the resolution fix recovered 233 memory entries that were being written to an unreachable path.

    Relevant to process point 2

    The "multiple divergent copies of the same helper" pattern bit again, in a different place: ~/.claude/settings.json's statusLine pointed at ~/.claude/plugins/marketplaces/ruflo/.claude/helpers/statusline.cjs, a clone pinned at claude-flow 3.26.0, while the installed CLI was 3.41.1. That copy's getPkgVersion() probes marketplaces/ruflo/package.json first, ahead of every global-install probe, so the statusline confidently reported RuFlo V3.26.0 for months — the marketplace clone reporting its own version as the user's. Different root cause, same shape: several copies of one helper, no single source of truth, and the stale one winning. Happy to file that separately if useful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions