Skip to content

Lazy skill first load records expected WorkspaceReadNotFoundError as a failed span #3889

Description

@Filimoa

Please read this first

Describe the bug

When LocalDirLazySkillSource loads a skill for the first time, it calls:

await session.read(skill_md_path, user=user)

to determine whether the skill is already materialized.

The file is expected not to exist on the first load, so the sandbox raises WorkspaceReadNotFoundError. LocalDirLazySkillSource.load_skill() catches the exception and successfully stages the skill, but tracing still records the sandbox.read operation as a failed/error span.

As a result, a successful skill load appears to contain an operational failure and can create noise in trace inspection and error monitoring.

The parent load_skill call succeeds with output similar to:

{
  "status": "loaded",
  "skill_name": "current-rate-rule-manual",
  "path": ".agents/current-rate-rule-manual"
}

but its child sandbox.read span is marked as failed with:

{
  "error_code": "workspace_read_not_found",
  "error_type": "WorkspaceReadNotFoundError",
  "error_retryable": false
}

This becomes annoying if you have agents using the traces to troubleshoot issues.

Debug information

  • Agents SDK version: reproduced with v0.17.6
  • Latest checked version: the same existence-probe implementation is still present in v0.18.3
  • Python version: Python 3.14
  • Configuration: SandboxAgent with Skills(lazy_from=LocalDirLazySkillSource(...))

Repro steps

Create a skill:

skills/
└── example-skill/
    └── SKILL.md
---
name: example-skill
description: Example skill used to reproduce lazy loading.
---

# Example skill

Follow these example instructions.

Run:

import asyncio
from pathlib import Path

from agents import Runner
from agents.run import RunConfig
from agents.sandbox import (
    Manifest,
    SandboxAgent,
    SandboxPathGrant,
    SandboxRunConfig,
)
from agents.sandbox.capabilities import (
    Capabilities,
    LocalDirLazySkillSource,
    Skills,
)
from agents.sandbox.entries import LocalDir
from agents.sandbox.sandboxes.unix_local import UnixLocalSandboxClient


async def main() -> None:
    skills_root = Path("skills").resolve()

    agent = SandboxAgent(
        name="Skill test",
        instructions=(
            "Load the example-skill skill, read its SKILL.md, and follow it."
        ),
        default_manifest=Manifest(
            extra_path_grants=(
                SandboxPathGrant(
                    path=str(skills_root),
                    read_only=True,
                ),
            ),
        ),
        capabilities=[
            *Capabilities.default(),
            Skills(
                lazy_from=LocalDirLazySkillSource(
                    source=LocalDir(src=skills_root),
                ),
            ),
        ],
    )

    run_config = RunConfig(
        sandbox=SandboxRunConfig(
            client=UnixLocalSandboxClient(),
        ),
        workflow_name="lazy-skill-repro",
    )

    result = await Runner.run(
        agent,
        "Use the example skill.",
        run_config=run_config,
    )
    print(result.final_output)


asyncio.run(main())

Inspect the resulting trace:

  1. load_skill completes successfully with status: loaded.
  2. A child sandbox.read span is nevertheless marked as failed with WorkspaceReadNotFoundError.
  3. The skill is subsequently written and can be read normally.

Expected behavior

First-time lazy skill loading should not produce an error span for the expected absence of SKILL.md.

Possible approaches could include:

  • adding a non-throwing exists() or stat() sandbox-session operation;
  • using a non-erroring existence check inside LocalDirLazySkillSource; or
  • treating this particular expected not-found result as a non-error trace outcome.

I would be happy to contribute a fix and regression test once the preferred approach is confirmed.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions