* fix(tools): sandbox FileWriterTool writes and fix file tool rough edges
FileReadTool confined reads to the working directory, but FileWriterTool
only checked that `filename` stayed inside `directory` — and `directory`
itself is an LLM-supplied schema field. An agent could therefore write
anywhere the process had permission to, including ~/.ssh and site-packages,
while the reader refused to read back what the writer had just written.
FileWriterTool was the only filesystem tool in the package that did not go
through validate_file_path; files_compressor_tool validates even its
output path.
Writes are now confined to base_dir (the working directory by default):
the resolved directory must sit inside base_dir, and the resolved file
must sit inside that directory. The pre-existing filename containment
check is kept as-is and still applies even when the unsafe-paths escape
hatch is on, so no existing guarantee is weakened.
Both tools gain a base_dir field so a developer can widen the sandbox
deliberately instead of reaching for the process-wide
CREWAI_TOOLS_ALLOW_UNSAFE_PATHS kill switch. FileReadTool also stops
rejecting a file_path given to its own constructor: that is
developer-declared intent, and declaring one file does not expose its
siblings.
Also fixed:
- FileReadTool scanned the whole file when reading a line window; it now
stops via islice once the requested lines are collected.
- FileWriterTool._run(**kwargs) made the documented positional call
signature raise TypeError and turned a missing overwrite into
"error accessing key". It now takes named parameters in the documented
(filename, content, directory) order.
- A directory naming an existing file reported "already exists and
overwrite option was not passed" even with overwrite=True; it now
explains the real problem.
- Subdirectories inside filename are created, matching what passing
directory already did.
- Both tools now write and decode UTF-8 by default instead of the
platform locale encoding, with an encoding field to override. The docs
already claimed UTF-8 and recommended the writer to Windows users.
- The writer's schema fields had no descriptions for the LLM.
- Docs claimed FileReadTool parses JSON into a dict (it never has),
shipped a snippet that raised TypeError, and did not mention the path
sandbox. The writer README also began with a stray "Here's the
rewritten README" preamble.
BREAKING CHANGE: FileWriterTool no longer writes outside the working
directory. Pass base_dir to authorize a different tree.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* chore: update tool specifications
* fix(tools): make the declared FileReadTool file reachable by agents
Addresses review feedback on #6692.
The constructor-path exemption did not actually work the way an agent
calls the tool. The description only advertises a redacted label (the
basename, when the file sits outside the sandbox), but resolution
required the exact absolute path, so the model's call was sandboxed and
the declared file was never read. Worse, file_path was a required schema
field, so the long-documented "call with no arguments to read the default
file" raised a validation error instead:
FileReadTool(file_path="/outside/declared.txt")
.run() -> ValueError: validation failed
.run(file_path="declared.txt") -> Error: File not found
.run(file_path="/outside/declared.txt") -> works, but the model was
never told this path
file_path is now optional in the schema, so omitting it reads the default,
and the declared file is addressable by the label the description shows
the model as well as by its real path. Declaring one file still does not
expose its siblings.
The declared path is also pinned to its real path at construction, so a
later chdir cannot silently repoint it at a different file — previously a
relative constructor path re-resolved against the new working directory
on every call.
Also guards the writer's filepath resolution, which could raise
ValueError out of _run for a filename containing a null byte, breaking
the contract of always returning a descriptive string. The directory and
read paths were already guarded.
Adds docstrings to strtobool and both _run methods, corrects an Arabic
tanween spelling and a kaf-as-descriptor calque in the localized read
docs, and regenerates tool.specs.json for the schema change.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(tools): anchor the declared read path to base_dir, not the cwd
Addresses the second round of review feedback on #6692.
The previous commit pinned a relative constructor file_path with
os.path.realpath, which anchors to the working directory, while both
format_path_for_display and validate_file_path anchor a relative path to
base_dir. With the two roots disagreeing, the same relative string meant
two different files — and the tool served the cwd one under a label that
looks like it belongs to the sandbox:
FileReadTool(file_path="data.txt", base_dir="/allowed") # cwd=/work
label advertised to the model -> "data.txt"
run(file_path="data.txt") -> contents of /work/data.txt
That reads a file from outside base_dir, so it was a sandbox escape
introduced by the exemption itself, not just a wrong-file bug.
Resolution now goes through a single _resolve_against_base helper that
anchors relative paths exactly the way the sandbox does, so the pinned
path, the advertised label and the containment check all agree. Covered
by test_relative_declared_path_anchors_to_base_dir.
Also softens "always readable" to "always allowed past the containment
check" in the docstring, README and docs, since bypassing containment
does not guarantee the read succeeds — it can still fail on a missing
file, a directory, or permissions.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(tools): tell the LLM about the path sandbox in tool descriptions
Addresses the low-confidence notes from the Copilot review on #6692.
Both tools' descriptions were pre-sandbox wording, so the model learned
about containment only by attempting a path and reading the error back.
Both now state that access is confined to the tool's allowed directory
and that a path resolving outside it is rejected.
The wording deliberately says "the tool's allowed directory" rather than
"the working directory", because the root is base_dir when one is set,
and naming the absolute root would leak it into the prompt — the same
reason paths are redacted in errors.
Not changed: the notes also suggested advertising `encoding`. That is a
constructor-only field the model cannot set, so describing it to the LLM
would be misleading.
Also fixes a test docstring that contradicted its own assertion — the
public run() path does raise on schema validation failure, which is what
the test asserts.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(tools): anchor base_dir at construction so the sandbox cannot move
Addresses the third review round on #6692.
Both remaining findings came from the same habit: storing an unanchored
string and re-resolving it later.
A relative base_dir was kept verbatim and re-resolved against getcwd() on
every call, while the declared file was pinned once at construction. After
a chdir the sandbox root moved but the declared default did not, so one
tool applied two different roots. base_dir is now resolved once — in the
reader's __init__, and via a field_validator on the writer so it also
applies on the model_validate path.
That also covers the serialization concern. model_dump drops the private
pin, and __init__ re-runs on restore, so a relative file_path was
re-anchored against whatever the working directory happened to be at load
time. With base_dir anchored, restore rebuilds the identical pin.
The residual case is a relative file_path with no base_dir, where the
sandbox root is the working directory too — so both move together and the
tool stays self-consistent. Covered by
test_declared_path_survives_a_serialization_round_trip and
test_relative_base_dir_is_anchored_at_construction on both tools.
Also corrects the writer's 'directory' description, README and docs: the
default resolves inside the tool's allowed directory, which is base_dir
when one is set, not always the working directory.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* feat(skills)!: promote Skills Repository out of experimental
The registry-backed Skills Repository (crewai skill create/publish/
install/list, @org/name refs, global cache) is now mainline:
- CLI: `crewai skill ...` is a top-level group; the CREWAI_EXPERIMENTAL
gate and the now-empty `crewai experimental` group are removed.
- Runtime: registry.py, cache.py, and events.py move from
crewai.experimental.skills into crewai.skills next to the loader;
the require_experimental_skills() gate is gone.
crewai.experimental.skills remains as a deprecated re-export shim.
- Docs: concepts/skills now leads with the CLI workflow and documents
the create -> publish -> install lifecycle.
Linear: n/a (requested promotion)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(skills): org-scoped publish only + docs in all languages
Skills are always scoped to the publishing organization, like tools:
drop the --public/--private flags from `crewai skill publish` and
always send is_public=False to the registry. CLI tests assert the flag
is rejected and the API never receives a public publish.
Translate the new CLI-first Quick Start and the create -> publish ->
install lifecycle section into ar, pt-BR, and ko concepts/skills docs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(skills): address review comments on the promotion PR
- Back-compat shim now aliases the old submodules in sys.modules so
`crewai.experimental.skills.registry/cache/events` imports (and patch
targets) resolve to the real crewai.skills modules, not just the
package-root re-exports.
- `crewai skill publish` actually enforces the git-state check that
--force claims to skip: unsynced repos block publishing (mirroring
tool publish); standalone skill dirs outside any git repo publish
without a check.
- Explicit UTF-8 encoding on SKILL.md and cache-metadata reads/writes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(skills): fail closed when git state cannot be validated on publish
Follow deploy's pattern: construct git.Repository(fetch=False) and only
treat "not a Git repository" as skippable — any other git error
(fetch/auth/misconfiguration) now blocks publish with a --force escape
hatch instead of silently bypassing the sync check.
Also single-style imports in the shim test (CodeQL) with the dotted
shim import covered via importlib.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(skills): fetch before sync check on publish; bump mcp past advisories
Publish now refreshes remote-tracking refs (repository.fetch()) before
is_synced(), so ahead/behind is judged against the actual remote rather
than stale local refs; a failing fetch blocks publish with the --force
escape hatch. Adds a fail-closed test for fetch errors.
Raise mcp to >=1.28.1,<2 (locks 1.28.1): the ~=1.26.0 pin blocked
GHSA-hvrp-rf83-w775 / GHSA-jpw9-pfvf-9f58 (fixed 1.27.2) and
GHSA-vj7q-gjh5-988w (fixed 1.28.1), which were failing pip-audit on
this PR.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Vinicius Brasil <vini@hey.com>
* docs: add Flows in Studio documentation
Documents the new Flows build mode in Studio: why deterministic
workflows with agentic steps matter, the three core node types
(Single Agent, Crews, Router), and Agent Repository publish/pull
sync across organizations.
Includes a rollout banner for the week of July 20th, English source
plus pt-BR, Korean, and Arabic translations, and nav entries for
both edge and v1.15.2 (Crew Studio group renamed to Studio).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: limit Flows in Studio docs to edge version
Versioned snapshots are updated by a separate script, so remove the
v1.15.2 copies and revert its nav changes; the page now lives only
under edge.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* docs: rename ACP rules to policies across edge and v1.15.1 locales with redirects
* docs: point ACP policies pages at the new policy screenshots
* docs: address review — revert frozen v1.15.1 edits and fix redirect ordering
* docs: prefix edge policies cross-links so they resolve until the next version cut
* docs: move policies redirects above all wildcard redirects
JSON-formatted stdout is now the only supported log shape in CrewAI
Enterprise — the `CREWAI_LOG_FORMAT=json` opt-in env var is gone and
no longer needs to be configured in AMP. Removes the "Enabling JSON
output" section, the env-var setup step, the troubleshooting check,
and the `legacy text mode` comparison across the four locale copies
(`en`, `ko`, `pt-BR`, `ar`) of `docs/edge/<lang>/enterprise/guides/datadog.mdx`.
The page itself already landed on main via #6247. This rebases onto main
and applies the two remaining changes:
- Nest crew-studio + merged-step-card into a collapsible "Crew Studio"
nav group (pencil icon), across edge and v1.14.7 in en, pt-BR, ko, ar.
- Remove the temporary "Rolling out" Note banner (feature ships today).
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: add "One Card per Step" Studio page (AGE-107)
Document the merge of the task and agent nodes into a single step card on
the Studio canvas. Written as evergreen present-tense feature docs with a
dated rollout banner (June 24th) for the pre-launch customer announcement;
the banner is the only time-bound content and is flagged for removal after
ship. Added in edge + v1.14.7 across en, pt-BR, ko, and ar, with nav entries
in docs.json and three canvas/editor/swap screenshots.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: bump bedrock agentcore dependencies
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: alex-clawd <alex@crewai.com>
Adds a consolidated `datadog.mdx` under `docs/edge/{en,pt-BR,ko,ar}/enterprise/guides/`
covering both the Datadog Agent path (stdout JSON logs via `CREWAI_LOG_FORMAT=json`)
and the Datadog OTLP intake, with a JSON log schema reference and a ready-to-import
operations dashboard (`datadog_dashboard.json`). Reframes `capture_telemetry_logs.mdx`
to lead with OpenTelemetry as the vendor-neutral path and point readers to the new
Datadog page for that ecosystem's setup.
* feat: adopt directory-based docs versioning with Edge channel
Switch docs.crewai.com from navigation-only versioning (every version
selector entry rendered the same docs/<lang>/* source files) to
Mintlify's directory-based versioning so each version selector entry
renders its own snapshot. Add an "Edge" channel under docs/edge/<lang>/*
that always reflects main HEAD for unreleased work, eliminating
pre-release leakage onto frozen release labels. External links to
canonical /<lang>/* URLs are preserved via wildcard redirects that
always land on the current default version.
Layout:
- docs/edge/<lang>/* rolling source (you edit here)
- docs/edge/enterprise-api.*.yaml
- docs/v<X.Y.Z>/<lang>/* frozen, immutable snapshots
- docs/v<X.Y.Z>/enterprise-api.*.yaml
- docs/images/ shared, append-only
- docs/docs.json nav + redirects
URLs follow the Mintlify-idiomatic shape: /edge/<lang>/<page> for
Edge, /v<X.Y.Z>/<lang>/<page> for every frozen snapshot. The wildcard
redirects /<lang>/:slug* -> /<default>/<lang>/:slug* keep stale links
working, and every freeze rewrites them (plus all per-section/per-page
redirects) so destinations always resolve to the current default
without depending on a second redirect hop.
Release flow integration (devtools release):
- New module crewai_devtools.docs_versioning.freeze() materialises
docs/v<X.Y.Z>/ from docs/edge/, rewrites openapi: refs inside the
snapshot, inserts the version into every language block in
docs.json, and refreshes all redirect destinations.
- _update_docs_and_create_pr() in cli.py now calls that freeze during
Phase 2 of devtools release. Edge changelogs are updated first (so
the snapshot freeze picks them up), then the snapshot is staged
alongside docs.json, branched as docs/freeze-v<X.Y.Z>, and the PR
is titled [docs-freeze] docs: snapshot and changelog for v<X.Y.Z>
— the title prefix the new CI guard reads.
- The PR still gates tag, GitHub release, PyPI publish, and the
enterprise release as before; no new PRs are added.
- Pre-releases (1.X.YaN, 1.X.YbN, ...) skip the snapshot — they ride
Edge — and the docs PR title omits the [docs-freeze] prefix.
- docs_check (AI-generated docs scaffolding) writes to
docs/edge/<lang>/* so newly-generated unreleased docs land in Edge
and never accidentally touch a frozen snapshot.
Migration scripts (one-shot):
- scripts/docs/freeze_historical_versions.py reconstructs all 16
historical snapshots (v1.10.0 .. v1.14.7) from git tags via
git archive | tar, rewriting openapi: MDX refs so each snapshot
reads its own enterprise-api YAML rather than the live one.
- scripts/docs/prefix_version_paths.py one-shot-migrates docs.json:
rewrites every page path in 16 versioned blocks to point under
docs/v<X.Y.Z>/, inserts a new Edge entry per language, tags
v1.14.7 as Latest (default), prunes pages whose target file
doesn't exist in the snapshot (e.g. docs/ar/ didn't exist before
v1.12.0), and writes the wildcard + per-section redirects.
- scripts/docs/freeze_current_edge.py is now a thin CLI wrapper
around docs_versioning.freeze for manual one-off freezes (e.g.
retroactively snapshotting a forgotten release).
CI guards (.github/workflows/docs-snapshots.yml):
- Frozen snapshots under docs/v[0-9]*/ are immutable; only PRs whose
title contains [docs-freeze] (i.e. release-cut PRs generated by
devtools release or the manual wrapper) may modify them.
- Images under docs/images/ are append-only since snapshots share a
single image directory. Deleting or renaming an image breaks every
historical snapshot that still references it.
Restored docs/images/crewai-otel-export.png from PR #3673; it was
deleted in PR #4908 but v1.10.0 / v1.10.1 snapshots still reference
it. Restoring instead of editing the snapshots preserves historical
rendering fidelity and validates the new append-only rule
retroactively.
Tests:
- lib/devtools/tests/test_docs_versioning.py covers the freeze: file
copy, openapi rewrite, version insertion, default demotion, redirect
upserts, per-section redirect rewriting, idempotency, and invalid
inputs.
Verified locally with mintlify broken-links: 0 broken links across
the full site (Edge + 16 frozen versions, 4 locales).
AGENTS.md (repo root) is the contributor guide for the new model;
RELEASING.md is the release-cut runbook; README's Contribution
section links to both.
Co-authored-by: Cursor <cursoragent@cursor.com>
* style: resolve linter issues
---------
Co-authored-by: Cursor <cursoragent@cursor.com>