{"record":{"id":"b2e7f32206fb5e43","repo":"langchain-ai/deepagents","slug":"debug-log-directory-is-not-a-real-directory-path","errorCode":null,"errorMessage":"debug log directory is not a real directory: {path}","messagePattern":"debug log directory is not a real directory: (.+?)","errorType":"exception","errorClass":"OSError","httpStatus":null,"severity":"error","filePath":"libs/code/deepagents_code/_debug.py","lineNumber":302,"sourceCode":"    valid = \", \".join(LOG_LEVELS)\n    message = f\"ignoring invalid {LOG_LEVEL}={raw!r}; expected one of {valid}\"\n    _warn(message)\n    return fallback\n\n\ndef _prepare_debug_directory(path: Path) -> None:\n    \"\"\"Create or tighten the debug directory to owner-only access.\n\n    Raises:\n        OSError: If the directory cannot be created, opened, or tightened.\n    \"\"\"\n    with contextlib.suppress(FileExistsError):\n        path.mkdir(mode=0o700)\n    if os.name == \"nt\":\n        metadata = path.lstat()\n        if stat.S_ISLNK(metadata.st_mode) or not stat.S_ISDIR(metadata.st_mode):\n            msg = f\"debug log directory is not a real directory: {path}\"\n            raise OSError(msg)\n        _set_windows_owner_only_dacl(path)\n        return\n    flags = os.O_RDONLY | getattr(os, \"O_DIRECTORY\", 0) | getattr(os, \"O_NOFOLLOW\", 0)\n    fd = os.open(path, flags)\n    try:\n        metadata = os.fstat(fd)\n        if metadata.st_uid != os.geteuid():\n            msg = f\"debug log directory is not owned by the current user: {path}\"\n            raise OSError(msg)\n        os.fchmod(fd, 0o700)\n    finally:\n        os.close(fd)\n\n\ndef _thread_log_name(thread_id: str) -> str:\n    \"\"\"Return a traversal-safe log filename for a thread identifier.\"\"\"\n    if (\n        len(thread_id) <= _MAX_THREAD_FILENAME_LENGTH","sourceCodeStart":284,"sourceCodeEnd":320,"githubUrl":"https://github.com/langchain-ai/deepagents/blob/a1af029e6e73cb17c36bff823d227747b28e91e1/libs/code/deepagents_code/_debug.py#L284-L320","documentation":"_prepare_debug_directory creates the per-thread debug log directory with mode 0o700 and then verifies it is a genuine directory and not a symlink (Windows check via lstat). If lstat reveals the path is a symlink or not a directory, the library refuses to write debug logs there to prevent symlink attacks on debug output. This is a security hardening error, not a normal usage failure.","triggerScenarios":"On Windows only: an attacker (or leftover artifact) replaced the debug log path with a symlink or a non-directory file between mkdir and the lstat check, and bind_debug_logging_to_thread was called.","commonSituations":"A stale symlink left at the debug directory location from a previous setup; a temp-cleanup tool replaced the directory with a junction; another process races to substitute a symlink at the path.","solutions":["Delete the path at the reported location (it is a symlink or a file, not a real directory) and let the library recreate it.","Ensure only trusted processes can write to the parent directory where the debug log dir is created.","Retry after removing the bad entry; the next bind_debug_logging_to_thread call will recreate it with mode 0o700."],"exampleFix":"// before (path is a symlink)\ndebug/logs -> /somewhere/else\n// after (remove and recreate)\nrm debug/logs\nmkdir debug/logs","handlingStrategy":"validation","validationCode":"import os, stat\np = Path(debug_dir)\nif p.is_symlink() or (p.exists() and not p.is_dir()):\n    p.unlink()  # remove bad entry before binding debug logging","typeGuard":"def is_real_directory(p: Path) -> bool:\n    try:\n        return not p.is_symlink() and stat.S_ISDIR(p.lstat().st_mode)\n    except OSError:\n        return False","tryCatchPattern":"try:\n    bind_debug_logging_to_thread(thread_id)\nexcept OSError as exc:\n    if 'not a real directory' in str(exc):\n        shutil.rmtree(path, ignore_errors=True) or os.remove(path)\n        bind_debug_logging_to_thread(thread_id)  # retry once","preventionTips":["Keep the debug log parent directory writable only by trusted processes/users.","Clean stale symlinks from temp/cache directories before startup.","On Windows, audit for junctions/symlinks at known dcode paths."],"tags":["security","filesystem","symlink","windows"],"backgroundTag":"symlink-attack-detected","analyzedSha":"a1af029e6e73cb17c36bff823d227747b28e91e1","analyzedAt":"2026-08-29T11:43:24.718Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}