iOfficeAI/OfficeCLI · error · CliException

plugin_contract_violation

plugin_contract_violation

Error message

Exporter plugin '{plugin.Manifest.Name}' reported success but no output file was written at {outPath}.

What it means

CliException (code 'plugin_contract_violation') thrown when the exporter exited 0 but File.Exists(outPath) is false — the plugin reported success yet wrote nothing to the declared output path.

Solutions

  1. Confirm the outPath directory exists and is writable.
  2. Reproduce and capture the exporter's actual output location via process/file tracing.
  3. Treat exit-0-with-no-file as a plugin bug and report upstream with the source file.
Defensive patterns

Strategy: validation

Validate before calling

var dir = Path.GetDirectoryName(outPath);
if (!Directory.Exists(dir)) Directory.CreateDirectory(dir!);
if (!HasWriteAccess(dir!)) throw new InvalidOperationException("outPath dir not writable.");

Try / catch

try { ExporterInvoker.Run(source, targetExt, outPath); }
catch (CliException ex) when (ex.Code == "plugin_contract_violation" && ex.Message.Contains("no output file was written"))
{ /* verify outPath dir; treat persistent case as plugin bug */ }

Prevention

When it happens

Trigger: Exporter returns exit 0 without creating/writing the file at outPath; wrote to a different path; output path points at an unwritable location and the plugin swallowed the error.

Common situations: Plugin bug (success path on empty conversion); outPath directory missing or read-only; plugin writes to a temp path and forgets to move; encoding/locale altering the path.

Related errors


AI-assisted analysis of iOfficeAI/OfficeCLI@1ced45e900 (2026-08-13). Data as JSON: /api/errors/b1b9447cd187b1bd. Report an issue: GitHub.

Appendix: source

Thrown at src/officecli/Core/Plugins/ExporterInvoker.cs:80

            };

        if (result.ExitCode != 0)
            throw new CliException(
                $"Exporter plugin '{plugin.Manifest.Name}' failed (exit {result.ExitCode}): {Truncate(result.Stderr, 500)}")
            {
                Code = result.ExitCode switch
                {
                    2 => "corrupt_input",
                    3 => "unsupported_feature",
                    4 => "license_expired",
                    5 => "protocol_mismatch",
                    6 => "plugin_idle_timeout",
                    _ => "plugin_failed",
                },
            };

        if (!File.Exists(outPath))
            throw new CliException(
                $"Exporter plugin '{plugin.Manifest.Name}' reported success but no output file was written at {outPath}.")
            { Code = "plugin_contract_violation" };

        return new ExportResult(outPath, plugin, residentClosed);
    }

    /// <summary>
    /// Find an exporter for (source, target). Indexed by target extension (the
    /// plugin's declared extensions field); filtered by source via the manifest's
    /// supports list. A plugin missing supports is assumed to accept all native
    /// sources — conservative default for older manifests.
    /// </summary>
    public static ResolvedPlugin? Resolve(string sourceExt, string targetExt)
    {
        var p = PluginRegistry.FindFor(PluginKind.Exporter, targetExt);
        if (p is null) return null;

        if (p.Manifest.Supports is null || p.Manifest.Supports.Count == 0)

View on GitHub (pinned to 1ced45e900)