Skip to content

/results/ has no resolver: FLUENT_DATA_DIR has no FLUENT_RESULTS_DIR counterpart (+ tests break when FLUENT_DATA_DIR is set) #30

Description

@Tazakoc

Version: 0.3.0, installed as a plugin (claude plugin install fluent@m98)

Two findings, both rooted in the same env-var/path-resolution layer.


1. Session results have no resolvable location

fluent_paths.py resolves the data directory properly — FLUENT_DATA_DIR, then $CLAUDE_PROJECT_DIR/data, then ./data, then ~/.claude/fluent-data — and exposes data_dir(), plugin_root() and backups_dir().

There is no results_dir() and no FLUENT_RESULTS_DIR. Instead, six skills hardcode a bare, root-anchored path:

  • fluent-learn/SKILL.md:150 — Save exchange to /results/fluent-learn-session-{NNN}.md
  • fluent-writing/SKILL.md:156, :219
  • fluent-reading/SKILL.md:205
  • fluent-review/SKILL.md:155
  • fluent-speaking/SKILL.md:168
  • fluent-session-analyzer/SKILL.md — reads /results/*.md

In plugin-install mode there is no repo root, so /results/ resolves against whatever the current working directory happens to be. For anyone running Claude Code from a directory that is not a fluent clone, session results land in an arbitrary place — in my case a knowledge vault's root, unrelated to where the six dbs live.

The read side makes it worse than misplaced files: fluent-session-analyzer looks in the same unresolved location. Redirect the write without redirecting the read and the analyzer finds nothing, then plans the next session from zero history — no error, just silently degraded tutoring.

Why a workaround isn't enough

FLUENT_DATA_DIR can move the dbs into a stable location, but nothing can move results/ short of editing the installed skills, which claude plugin update regenerates. The remaining options are a prose instruction telling the model where to write instead (fragile — it competes with the more specific Save exchange to /results/… line the skill itself supplies mid-session) or a filesystem symlink at the CWD root (invasive, and doesn't survive a clone on another machine).

Suggested fix

Add to fluent_paths.py, mirroring data_dir() exactly:

@lru_cache(maxsize=1)
def results_dir() -> Path:
    """Resolve the session-results directory (pure — does not create it)."""
    env = os.environ.get("FLUENT_RESULTS_DIR")
    if env:
        return Path(env).expanduser().resolve()
    return data_dir().parent / "results"


def ensure_results_dir() -> Path:
    d = results_dir()
    d.mkdir(parents=True, exist_ok=True)
    return d

Defaulting to data_dir().parent / "results" keeps clone-mode behaviour identical (./data → ./results) while giving plugin-mode installs a location that tracks wherever the learner's data already lives. Then replace the six hardcoded /results/ strings with a reference to it, the same way the skills already resolve the data dir via the helper.

Happy to open a PR if the approach looks right.


2. The test suite fails whenever FLUENT_DATA_DIR is set

tests/test_update_db.py builds a fixture DB in a temp dir, but does not clear FLUENT_DATA_DIR from the subprocess environment. Since fluent_paths.data_dir() gives that variable top precedence, the scripts under test read the real data directory instead of the fixture.

$ FLUENT_DATA_DIR=/some/real/path python3 -m unittest discover -s tests
...
AssertionError: 2 != 0 : b"[Fluent] Error loading databases: [Errno 2] No such file or directory: '…/learner-profile.json'"
Ran 12 tests in 1.051s
FAILED (failures=11)

$ env -u FLUENT_DATA_DIR python3 -m unittest discover -s tests
Ran 12 tests in 1.429s
OK

11 of 12 fail. FLUENT_DATA_DIR is the variable the README tells users to export for multiple learners (export FLUENT_DATA_DIR=~/.fluent/dutch), so the suite is broken for exactly the audience following the documented setup — and it fails in CI-invisible fashion for anyone who has it in their shell profile.

Suggested fix

Scrub the resolution env vars in setUp, so tests are hermetic regardless of the developer's environment:

env = {k: v for k, v in os.environ.items()
       if k not in ("FLUENT_DATA_DIR", "FLUENT_RESULTS_DIR", "CLAUDE_PROJECT_DIR")}
env["FLUENT_DATA_DIR"] = str(self.tmp_data)   # point at the fixture explicitly

Explicitly pinning it at the fixture is better than merely unsetting it, since it also stops the tests from silently falling through to ~/.claude/fluent-data.


Environment

Windows 11, Python 3.13.13, Claude Code CLI, plugin installed at project scope. Otherwise the install validated cleanly end-to-end: read-db.py, update-db.py, validate-data.py, session-start.py and session-end.py all behaved correctly, and SM-2 produced textbook first-exposure values (easiness_factor 2.5, interval_days 1, repetitions 0, due_date = created + 1). Nice project — thanks for building it.

Activity

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