* feat: add project_id to link OSS usage to an enterprise account
Adds a stable per-project identifier so a project's OSS traces and runs can
be attributed to an account after signup. There was no such identifier
before: [tool.crewai] held only `type`, the deploy UUID was printed to the
console but never persisted, Settings.org_uuid is global rather than
per-project, and trace batches carried only crew_fingerprint/crew_name.
The id lives in the project's pyproject.toml, so it is committed with the
repository and stays stable across machines, teammates, CI, and containers -
unlike a machine- or user-derived identifier, which is unstable in exactly
the containerized production environments that matter most.
crewai-core:
- get_project_id(): read-only lookup of [tool.crewai].project_id. Safe for
library code; never creates or modifies anything.
- get_or_create_project_id(): mints a uuid4 and persists it, returning
(id, created) so callers can tell the user. Best-effort - returns
(None, False) for a missing, malformed, or read-only pyproject.toml rather
than raising.
- Insertion edits the raw TOML text instead of round-tripping through a
writer, so comments, key order, and formatting elsewhere survive. The key
is placed at the end of the [tool.crewai] table, before the next table
header, so it cannot land in a neighbouring section.
- LoginPayload and TraceExecutionContext gain optional project_id.
Sent on two paths:
- Traces: project_id is added to execution_context, which is sent on both
the ephemeral and authenticated paths, so a project's traces remain
attributable before and after the user creates an account.
- Login: `crewai login` already sends the pseudonymous user_identifier on an
authenticated request; adding project_id means one request carries account
+ user + project, which is the link itself.
Minting is restricted to CLI commands the user explicitly invoked - `crewai
create` for new projects and `crewai run` to backfill existing ones - and is
announced when it happens. Library code only ever reads. Silently rewriting
a user's pyproject.toml during Crew.kickoff() would be surprising.
Privacy: project_id is a random uuid4 in a file the user commits. It is
visible in a diff, contains nothing personal, and identifies a project
rather than a person - so this needs none of the notice changes that
attaching a user identifier to all telemetry would require.
Tests: 18 new tests covering minting, stability, table placement, comment
and formatting preservation, five pyproject layouts, the neighbouring-table
regression, and graceful handling of missing/malformed/read-only files.
Verified end-to-end that both create paths mint distinct ids, that the trace
payload carries project_id on both the ephemeral and authenticated paths,
and that the login payload carries user_identifier and project_id together.
Follow-ups, deliberately not included: adding project_id to telemetry spans,
and backend persistence of the (account, user_identifier, project_id) triple.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UNumDnNbiyw3pv1WakAe6t
* refactor: drop the console announcement when minting project_id
Minting now happens silently. With no message to print, the (id, created)
tuple had no consumer, so simplify the API rather than keep the flag around
for a hypothetical caller:
- get_or_create_project_id() returns `str | None` instead of
`tuple[str | None, bool]`.
- Remove crewai_cli.utils.ensure_project_id, which existed only to print the
message and discard the flag. The four call sites (crewai create crew,
crewai create flow, crewai run, and tool-repository login) now call
get_or_create_project_id directly.
- Update tests for the simplified signature; still 18 tests covering minting,
stability, table placement, formatting preservation, five pyproject
layouts, and missing/malformed/read-only handling.
Behaviour is otherwise unchanged: minting stays restricted to CLI commands
the user invoked, library code still only reads via get_project_id, and a
missing or read-only pyproject.toml still returns None rather than raising.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UNumDnNbiyw3pv1WakAe6t
* fix: harden project_id minting against TOML corruption; address review
Several reviewers found ways the raw-text edit could produce invalid TOML.
Each is now fixed and covered by a test that fails without the fix.
Duplicate project_id key (Cursor bugbot, Copilot x2):
- get_project_id() reports a blank or non-string value as "absent", so a file
containing `project_id = ""` took the insert path and gained a second
project_id line - a duplicate key, and therefore invalid TOML that no
tomli-based tool could read afterwards.
- _insert_project_id is now _set_project_id: it replaces an existing
assignment inside [tool.crewai] instead of appending unconditionally.
Table header with a trailing comment (CodeRabbit major, Cursor bugbot):
- `[tool.crewai] # config` is valid TOML but failed exact string equality,
so the fallback appended a second [tool.crewai] header - a redefined table,
also invalid TOML, and silent because get_project_id swallows the resulting
decode error.
- Added _is_table_header(), which tolerates a trailing comment and does not
match similar names such as [tool.crewai-extra].
Writing into malformed TOML (Cursor bugbot, Copilot):
- get_or_create_project_id relied on get_project_id, which cannot distinguish
"no id" from "unparsable file", so it appended to files it could not parse.
- The locked path now parses explicitly and bails on a decode error, and
re-parses the updated content before writing, so this feature can never be
the reason a project's pyproject.toml stops parsing.
Concurrency and atomicity (CodeRabbit major):
- Two CLI processes could both see no id, mint different uuids, and clobber
each other, leaving a caller holding an id that is not on disk. Minting now
takes the existing crewai_core cross-process lock, re-reads under it, and
returns the id that persists.
- Writes go through a temp file in the same directory plus os.replace, so an
interruption cannot truncate pyproject.toml. File mode is copied across, and
the temp file is removed on failure.
- os.replace only needs a writable directory, which would have let an atomic
write silently overwrite a file the user marked read-only; writability is
now checked explicitly so that case still returns None.
Line endings (CodeRabbit):
- Path.read_text/write_text normalized CRLF to LF, so minting would rewrite a
CRLF-committed file entirely. Read and write now use newline="" and the
inserted line ending is derived from the existing content.
Default create path skipped minting (Cursor bugbot):
- `crewai create crew` defaults to create_json_crew; only the --classic and
flow paths minted, so most new projects had no id until a later command.
Wired into create_json_crew as well. Verified all three paths now mint
distinct ids.
Do not mint during login (CodeRabbit major):
- ToolCommand.login ran get_or_create_project_id, which is outside the
sanctioned minting commands and is invoked by `crewai tools create` from a
freshly scaffolded directory before the project is persisted. It now uses
the read-only get_project_id. Verified login leaves pyproject.toml
untouched.
Not applied: Copilot asked for a console message when an id is written, in
create_crew and create_flow. Minting was made deliberately silent in the
previous commit, so the (id, created) tuple and the announcement are both
gone by design.
Tests: 32 in test_project_id.py, up from 18. New cases cover blank and
non-string existing ids, three commented-header forms, similar table names,
malformed input, CRLF and LF preservation, concurrent minting convergence,
file-mode preservation, and temp-file cleanup. Confirmed the header and
duplicate-key tests fail when the fixes are reverted.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UNumDnNbiyw3pv1WakAe6t
* fix: never create [tool.crewai], treat whitespace ids as absent, harden test
`crewai run` could rewrite unrelated projects (Cursor bugbot, high):
- get_or_create_project_id ran before the cwd was established as a CrewAI
project, and _set_project_id appended a [tool.crewai] table when none
existed. Any directory with a pyproject.toml could therefore gain one -
including on `crewai run --definition`, which may otherwise succeed.
- _set_project_id no longer creates the table; it returns None when
[tool.crewai] is absent, so a key is only ever added to a table the project
already declares. The templates all ship the table, so no create path needs
the old fallback.
- The minting call in run_crew moved after the --definition early return, so
an explicit-flow run does not touch the cwd at all.
- Presence is checked, not truthiness: an empty [tool.crewai] is still a
CrewAI marker, and get_crewai_project_config returns {} both for that and
for an absent table.
- Verified an unrelated project's pyproject.toml is byte-identical after a
mint attempt.
Whitespace-only project_id accepted as valid (CodeRabbit):
- `project_id = " "` is truthy, so it was returned as an identity and would
have propagated into login payloads and tracing context. It also meant the
'" "' parameter of the replacement test asserted nothing.
- Added _usable_project_id, which strips before deciding, used by both
get_project_id and the locked mint path.
Concurrency test could hang CI (CodeRabbit, major):
- Neither the barrier nor the joins had timeouts, so a thread dying early or
blocking on the lock would hang the job rather than fail it. The result
count was also unchecked, so a dead thread still passed.
- Added timeouts, an explicit liveness assertion, a result-count assertion, a
lock around the shared result list, and corrected the docstring: this covers
the read-modify-write race with threads, not the cross-process backend.
Tests: 35, up from 32. New coverage for the absent-table refusal and three
whitespace forms; the blank-id replacement case now asserts a real uuid
replaced the blank value.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UNumDnNbiyw3pv1WakAe6t
* chore(deps): force gitpython 3.1.57+ for GHSA-p538-c434-8v24 and GHSA-3f7w-8rr8-f37f
Unrelated to project_id; bundled here only because it blocks this PR's
vulnerability scan. Two advisories were published for gitpython 3.1.55 after
main last passed the scan:
- GHSA-p538-c434-8v24: arbitrary file truncation via `git rev-list --output`
argument injection. Fixed in 3.1.56.
- GHSA-3f7w-8rr8-f37f: unguarded git option forwarding in
IndexFile.checkout() and TagReference. Fixed in 3.1.57.
- Bump the override floor to gitpython>=3.1.57 and declare the same floor in
crewai-tools, so consumers installing the published package are covered and
not only this repo's lock.
- 3.1.57 was published 2026-07-26, past gitpython's exclude-newer-package
cutoff of 2026-07-24, so that cutoff moves to 2026-07-27. Without it the
floor is unresolvable.
pip-audit against the updated lock reports no known vulnerabilities.
Verified gitpython 3.1.57 resolves and that crewai_tools and crewai_cli.git
still import.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UNumDnNbiyw3pv1WakAe6t
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit adds the organization ID parameter to the PlusAPI client, in
addition to the settings file. This allows for settings the organization
programmatically.
Inline agent and crew actions can now use repository-backed agents
without duplicating role, goal, and backstory in each definition.
Examples:
* `agent.with.from_repository: support_specialist`
* `crew.with.agents.researcher.from_repository: researcher`
`PlusAPI.get_agent` now uses the shared synchronous request path so
project loaders can fetch repository agents without nested event loops.
* Require explicit CrewAI project definitions
JSON crews and declarative flows now resolve from `[tool.crewai]`
metadata instead of implicit filename discovery. This makes project type
selection deterministic, prevents stray `crew.json(c)` files from changing
CLI behavior, and centralizes definition path validation for run, install,
deploy validation, plotting, and memory reset paths.
`[tool.crewai].definition` must be a project-local file path. Absolute
paths, `~`, missing files, directories, and paths escaping the project root
are rejected so deploy and runtime commands use the same contract.
Breaking changes and migration paths:
* JSON crew projects are no longer discovered from `crew.json` or
`crew.jsonc` alone. Add explicit metadata:
```toml
[tool.crewai]
type = "crew"
definition = "crew.jsonc"
```
* Declarative flow projects must use a valid project-local definition path:
```toml
[tool.crewai]
type = "flow"
definition = "flows/research.yaml"
```
* `Flow.from_definition(definition)` is removed. Use:
```python
Flow.from_declaration(contents=definition)
```
* `FlowDefinition.to_json()` and `FlowDefinition.to_yaml()` are removed.
Use `FlowDefinition.to_dict()` and serialize with the caller's JSON or
YAML library.
* `FlowDefinition.from_dict()` is removed. Use:
```python
FlowDefinition.from_declaration(contents=data)
```
* `FlowDefinition.json_schema()` is removed. Use Pydantic's schema API only
where schema generation is intentionally needed:
```python
FlowDefinition.model_json_schema(by_alias=True)
```
* `crewai_cli.run_crew.find_crew_json_file()` and `_has_json_crew()` are
removed. Use `configured_project_json_crew()` or the shared
`crewai_core.project.configured_project_definition("crew")` helper.
* `crewai reset-memories` now only loads JSON crews declared through
`[tool.crewai].definition`, and invalid declared JSON crew definitions
fail instead of silently falling back to classic crew discovery.
* Address code review comments
* fix: enforce owner-only permissions on credential files
Credentials stored at rest were left world-readable on multi-user hosts:
- TokenManager._get_secure_storage_path() documented its credential dir as
mode 0o700 but created it via mkdir() with default perms (0o755), leaving
the Fernet secret.key and encrypted tokens.enc in a traversable dir.
- Settings.dump() persisted tool_repository_password (plaintext) to
settings.json via open("w"), producing a 0o644 file, and created the
config dir at 0o755 — despite the sibling token_manager already writing
secrets atomically at 0o600.
Fixes:
- TokenManager: chmod the credential dir to 0o700 after mkdir (robust against
umask and pre-existing dirs).
- Settings: write settings.json atomically at 0o600 (mkstemp + chmod +
os.replace) and chmod the dedicated config dir to 0o700. The /tmp and cwd
fallback parents are deliberately not chmod'd; the 0o600 file mode protects
the credential there.
Adds regression tests asserting 0o600 files and 0o700 dirs, and that shared
fallback dirs are not globally tightened.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Potential fix for pull request finding 'Empty except'
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
* Potential fix for pull request finding 'Empty except'
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
* Close temp fd on secure settings write failure
* Log secure settings fd close failures
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
The previous `~=1.34.0` pin kept us on the unmaintained 1.34 line —
last patched as `1.34.1` in June 2025, eight minor releases behind
upstream — and caused `_create_exp_backoff_generator` `ImportError`
crashes in factory deployments where the OpenTelemetry Operator's
injected init container shadows
`opentelemetry.exporter.otlp.proto.common._internal` with >=1.35 while
our `opentelemetry-exporter-otlp-proto-grpc==1.34.1` still imports the
removed private symbol. Pinning to `~=1.42.0` tracks the current
upstream stable line; the resolver now lands on 1.42.1 and our public
OTel trace API usage is unaffected.
* feat: update pyproject.toml to specify wheel targets
Added a new section to the pyproject.toml file to include only specific files in the wheel build, enhancing the packaging process. Updated tests to verify the inclusion of these targets.
* feat: add memory save event handling to activity log
Implemented event handlers for MemorySaveStartedEvent, MemorySaveCompletedEvent, and MemorySaveFailedEvent in the crew_run_tui module. This allows the application to log memory save operations, capturing their status and details in the activity log. Added corresponding tests to verify the correct logging behavior for successful and failed memory saves.
* feat: enhance memory save event handling in activity log
Added functionality to suppress nested memory save events and updated the handling of MemorySaveStartedEvent, MemorySaveCompletedEvent, and MemorySaveFailedEvent to improve logging accuracy. Introduced new tests to verify the correct behavior of memory save events, including scenarios for nested events and completion updates for timed-out entries.
* Fix memory save activity log handling
* Normalize alpha package versions
* Update scaffolded crew dependency
* feat: add button to copy setup instructions for CrewAI coding agents
Introduced a button in the documentation that allows users to easily copy setup instructions for CrewAI coding agents. The instructions include installation steps, environment setup, and best practices for using the CrewAI CLI. This enhancement aims to streamline the onboarding process for new users.
* Improve missing CrewAI install guidance
* fix: address pr review feedback
* fix: avoid mismatched memory save rows
* fix: wait for queued memory save events
* fix: avoid matching memory saves on missing ids
* chore: normalize prerelease version to 1.14.8a1
* Update crewAI CLI with various enhancements and fixes
- Updated `create_json_crew.py` to require `crewai[tools]>=1.14.7`.
- Enhanced `git.py` with improved repository initialization, including automatic initial commit creation and exclusion patterns for initial commits.
- Modified `install_crew.py` to allow error handling during installation with an optional `raise_on_error` parameter.
- Expanded `plus_api.py` to include methods for creating and updating crews from ZIP files.
- Introduced a new `archive.py` for creating deployable ZIP archives of CrewAI projects, ensuring local artifacts are excluded.
- Updated `run_crew.py` to manage JSON crew dependencies and run crews in the project's environment.
- Enhanced deployment logic in `main.py` to handle ZIP uploads and improve user feedback during deployment processes.
- Added tests for new functionalities and ensured existing tests reflect recent changes in behavior and requirements.
* fix(cli): address deploy zip review feedback
* fix(cli): sync missing lockfile before deploy
* fix(cli): preserve remote deploy on git setup warnings
* test(cli): use single deploy main import style
* fix(cli): skip project install for json crew sync
* fix(cli): load json runner from source checkout
* fix(cli): skip json crew sync when locked
* fix(cli): address deploy zip review feedback
* fix(cli): pass env on zip redeploy
* fix(cli): harden json run and zip fallback
* fix(cli): validate before deploy lock install
* fix(cli): respect poetry lock for json runs
* fix(cli): align json zip wrapper detection
* fix(deps): bump starlette audit floor
* fix(cli): avoid auth retry for deploy exits
* fix(cli): update json zip script entrypoints
* feat(lock_store): make locking backend overridable
Allow the centralised lock factory to use a pluggable backend instead of
the hardcoded Redis/file selection. Backends are resolved with precedence
override > CREWAI_LOCK_FACTORY env > built-in default:
- set_lock_backend()/reset_lock_backend() and a scoped lock_backend()
context manager for programmatic overrides
- CREWAI_LOCK_FACTORY="module:callable" env import-path, resolved lazily
and cached, with clear errors on malformed or non-callable specs
- LockBackend Protocol documenting the contract (raw name in, context
manager out; backend owns its namespacing)
Default Redis/file behavior is unchanged when nothing is overridden.
* refactor(lock_store): use explicit body for LockBackend protocol method
Replace the no-op `...` body with `raise NotImplementedError` to satisfy
the CodeQL ineffectual-statement check while keeping the Protocol
structural-typing only.
* refactor(lock_store): drop scoped lock_backend context manager
Keep the backend overridable via set_lock_backend/reset_lock_backend and
the CREWAI_LOCK_FACTORY env path, but remove the scoped lock_backend()
context manager. It was speculative surface and the only thread-unsafe
piece (racy save/restore of the module global); nothing depends on it.
* refactor(lock_store): drop reset_lock_backend alias
reset_lock_backend() was just set_lock_backend(None); callers use that
directly. Clearing the override is documented on set_lock_backend.
* style(lock_store): apply ruff format
* refactor(lock_store): simplify overridable backend to a single setter
Reduce the override surface to just set_lock_backend(): lock() uses the
custom backend when one is set, otherwise the unchanged Redis/file default.
Drop the CREWAI_LOCK_FACTORY env import-path, the runtime_checkable
Protocol, the precedence resolver, and the getter — a custom backend is
now any callable(name, *, timeout) -> context manager, registered in
process.
* fix(lock_store): snapshot backend to avoid check-then-call race
Read the module-global backend once into a local before the None check
and the call, so a concurrent set_lock_backend(None) cannot make lock()
invoke None.
* docs(lock_store): clarify name handling for custom backends
The default namespaces the lock name; custom backends receive it
verbatim. Correct the lock() docstring which implied namespacing always
happens.
* docs(lock_store): note set_lock_backend is for one-time startup setup