Skip to content

fix(utils): allow nested output directories (check nearest existing ancestor for writeability)#92

Open
Osamaali313 wants to merge 2 commits into
MiniMax-AI:mainfrom
Osamaali313:fix/writeable-check-nested-output-dir
Open

fix(utils): allow nested output directories (check nearest existing ancestor for writeability)#92
Osamaali313 wants to merge 2 commits into
MiniMax-AI:mainfrom
Osamaali313:fix/writeable-check-nested-output-dir

Conversation

@Osamaali313

Copy link
Copy Markdown

Problem

build_output_path creates the output directory with mkdir(parents=True, exist_ok=True) — which can create several missing levels at once — but guards it with is_file_writeable, which only checks the immediate parent:

def is_file_writeable(path: Path) -> bool:
    if path.exists():
        return os.access(path, os.W_OK)
    parent_dir = path.parent
    return os.access(parent_dir, os.W_OK)   # immediate parent only

For a multi-level new output directory (e.g. <base>/a/b/c where <base>/a doesn't exist yet), the immediate parent <base>/a/b is itself missing, so os.access(<base>/a/b, W_OK) returns False and build_output_path raises:

MinimaxMcpError: Directory (<base>/a/b/c) is not writeable

…even though mkdir(parents=True) would create the whole chain without any problem. So passing a nested output_directory (e.g. audio/2026/06) to any audio/image/video/music tool fails before the result can be saved.

Fix

Check the writeability of the nearest existing ancestor — the level mkdir(parents=True) actually starts writing into — instead of only the immediate parent:

def is_file_writeable(path: Path) -> bool:
    if path.exists():
        return os.access(path, os.W_OK)
    for ancestor in path.parents:
        if ancestor.exists():
            return os.access(ancestor, os.W_OK)
    return False

Single-level new paths (immediate parent exists) behave exactly as before — no regression.

Tests

Extends test_is_file_writeable with a multi-level new path and adds test_build_output_path_creates_nested_dir (creates a/b/c under a temp dir). The multi-level branch was previously untested (test_make_output_path uses is_test=True, which bypasses the writeability check). pytest tests/test_utils.py passes 7/7.

(Distinct from the open PRs — this is in is_file_writeable/build_output_path, not the validation/timeout/play_audio areas.)

`build_output_path` creates the output directory with
`mkdir(parents=True, exist_ok=True)`, which may create several missing
levels. But its `is_file_writeable` guard only checked the *immediate*
parent: for a multi-level new path (e.g. `<base>/a/b/c` where `<base>/a`
does not exist), `os.access(parent, W_OK)` is False, so it raised
"Directory (...) is not writeable" even though `mkdir(parents=True)` would
create it fine.

Check the writeability of the nearest *existing* ancestor instead -- the
level `mkdir(parents=True)` actually starts writing into. Single-level new
paths (immediate parent exists) behave exactly as before.

Adds tests for a multi-level `is_file_writeable` path and for
`build_output_path` creating a nested directory.
Copilot AI review requested due to automatic review settings June 15, 2026 20:11

Copilot AI 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.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds regression coverage and fixes path writeability detection so nested, non-existent output directories are treated as writable when an existing ancestor is writable.

Changes:

  • Update is_file_writeable() to check the nearest existing ancestor instead of only the immediate parent.
  • Add tests covering multi-level non-existent paths and verifying build_output_path() creates nested directories.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
tests/test_utils.py Adds regression tests for multi-level path writeability and nested directory creation via build_output_path().
minimax_mcp/utils.py Fixes writeability checks for non-existent nested paths by walking up to the nearest existing ancestor.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread minimax_mcp/utils.py Outdated
Comment on lines +13 to +21
if path.exists():
return os.access(path, os.W_OK)
parent_dir = path.parent
return os.access(parent_dir, os.W_OK)
# The path does not exist yet. Callers create it with
# ``mkdir(parents=True)``, which may create several missing levels, so
# check writeability of the nearest existing ancestor rather than only the
# immediate parent (which is itself missing for a multi-level new path).
for ancestor in path.parents:
if ancestor.exists():
return os.access(ancestor, os.W_OK)
Comment thread tests/test_utils.py
Comment on lines +19 to +21
# Multi-level new path: the immediate parent does not exist yet, but
# mkdir(parents=True) can create it, so it must be reported writeable
# (regression: only the immediate parent used to be checked).
Comment thread tests/test_utils.py
Comment on lines +26 to +27
# A nested, not-yet-existing output directory must be created rather than
# rejected as "not writeable".
Address review: creating a child entry in a directory needs both write and
search (execute) permission, so check os.W_OK | os.X_OK on the nearest
existing ancestor (always a directory) rather than os.W_OK alone. No change
on platforms where directories are always searchable; on POSIX it avoids a
false positive for a write-but-not-executable directory.
@Osamaali313

Copy link
Copy Markdown
Author

Thanks @copilot

  1. W_OK | X_OK for directories. Good catch, adopted in e5f0ab8. The nearest-existing-ancestor we check is always a directory we'll create entries in, so it now checks os.W_OK | os.X_OK (write + search/execute). Verified it keeps the existing behavior on platforms where directories are always searchable and the tests still pass (7/7); on POSIX it avoids a false positive for a write-but-not-executable directory. I left the path.exists() branch as W_OK since path there may be a regular file (writability ≠ executability), keeping that case's original semantics.

2 & 3. "writeable" vs "writable" in comments. I kept "writeable" deliberately, to match this module's existing public API — the function is named is_file_writeable() and build_output_path raises "Directory (...) is not writeable". Using the same spelling keeps the comments consistent with the symbols they document. Happy to switch the whole module to "writable" if you'd prefer to standardize the spelling, but that felt out of scope for this fix.

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.

2 participants