alibaba/spring-ai-alibaba · warning

Failed to delete temporary directory

Error message

Failed to delete temporary directory: {}

What it means

ShellSessionManager.doCleanup deletes the session's temporary workspace directory when the session was created with a temporary workspace (useTemporaryWorkspace). If deleteDirectory throws IOException, this warning is logged with the directory path and the cleanup continues by removing SESSION_PATH_CONTEXT_KEY from the context. The temp dir may then be left on disk as an orphan.

Solutions

  1. Ensure all child processes of the shell session are terminated before cleanup so file handles are released.
  2. Check filesystem permissions/ownership of the temp directory (java.io.tmpdir) for the user running the agent.
  3. Monitor for the warning and add an external reaper (cron/task) to sweep orphaned temp dirs as a safety net.
  4. Set a custom, writable workspace path instead of the default temporary workspace if the platform's tmpdir is restricted.

Example fix

// before
// processes still alive when doCleanup runs
session.close();
// after
session.close();
process.children().forEach(ProcessHandle::destroyForcibly); // release file locks
// then doCleanup deletes tempDir successfully
Defensive patterns

Strategy: validation

Validate before calling

static void verifyDeletable(Path dir) throws IOException {
    if (dir == null || !Files.exists(dir)) return;
    if (!Files.isWritable(dir)) throw new IOException("No write permission on temp dir: " + dir);
}

Try / catch

try {
    deleteDirectory(tempDir);
} catch (IOException e) {
    log.warn("Failed to delete temporary directory: {}", tempDir, e);
    // schedule for later cleanup or reaper
}

Prevention

When it happens

Trigger: doCleanup (called from cleanup) attempts Files-style recursive deletion of tempDir and an IOException occurs — files locked or still being written by a lingering child process, permission problems, or a directory removed externally mid-deletion.

Common situations: 1) A spawned background process from the shell session still holding files open in the temp workspace. 2) Running agents as different users (e.g. containerized) without delete permission on the created dir. 3 Read-only filesystems or disk-full edge cases. 4 Temp dirs accumulating because this warning repeats and nobody notices.

Understand the failure class

Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.

Related errors


AI-assisted analysis of alibaba/spring-ai-alibaba@f82da0b50f (2026-09-09). Data as JSON: /api/errors/5a053aebeeba7d99. Report an issue: GitHub.

Appendix: source

Thrown at spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/tools/ShellSessionManager.java:203

				session = registryEntry.session();
				tempDir = registryEntry.workspacePath();
				log.debug("Removed shell session from global registry for threadId: {}", config.threadId().get());
			}
		} else if (session != null && config.threadId().isPresent()) {
			// Also remove from registry if we found it in context
			SESSION_REGISTRY.remove(config.threadId().get());
		}

		if (session != null) {
			session.stop(terminationTimeout);
			config.context().remove(SESSION_INSTANCE_CONTEXT_KEY);
		}

		if (tempDir != null && useTemporaryWorkspace) {
			try {
				deleteDirectory(tempDir);
			} catch (IOException e) {
				log.warn("Failed to delete temporary directory: {}", tempDir, e);
			}
			config.context().remove(SESSION_PATH_CONTEXT_KEY);
		}
	}

	/**
	 * Attempt to recover a shell session from the global registry.
	 * This is used during HITL resume when the original context is lost.
	 *
	 * @param config the runnable config containing threadId
	 * @return the recovered session, or null if not found
	 */
	private ShellSession recoverSessionFromRegistry(RunnableConfig config) {
		return config.threadId().map(threadId -> {
			SessionEntry entry = SESSION_REGISTRY.get(threadId);
			if (entry != null) {
				log.info("Recovered shell session from global registry for threadId: {}. " +
						"Shell state (working directory, environment) is preserved.", threadId);

View on GitHub (pinned to f82da0b50f)