Expo Build incorrectly identifies project as bare workflow

Hi - I’m trying to build and it appears expo-cli is incorrectly identifying my project as a bare workflow project, but only during the build process.

“expo diagnostics”

  Expo CLI 4.12.1 environment info:
      OS: macOS 11.6
      Shell: 5.8 - /bin/zsh
      Node: 16.10.0 - /opt/homebrew/bin/node
      Yarn: 1.22.13 - /opt/homebrew/bin/yarn
      npm: 7.24.0 - /opt/homebrew/bin/npm
      Watchman: 2021.09.27.00 - /opt/homebrew/bin/watchman
      CocoaPods: 1.10.2 - /opt/homebrew/lib/ruby/gems/3.0.0/bin/pod
      iOS SDK:
        Platforms: iOS 15.0, DriverKit 20.4, macOS 11.3, tvOS 15.0, watchOS 8.0
      Android Studio: 2020.3 AI-203.7717.56.2031.7583922
      Xcode: 13.0/13A233 - /usr/bin/xcodebuild
      expo: 42.0.4 => 42.0.4 
      react: 16.13.1 => 16.13.1 
      react-dom: 16.13.1 => 16.13.1 
      react-native: https://github.com/expo/react-native/archive/sdk-42.0.0.tar.gz => 0.63.2 
      react-native-web: ^0.17.5 => 0.17.5 
    Expo Workflow: managed

“expo build”

⚠️  expo build:ios currently only supports managed workflow apps.
If you proceed with this command, we can run the build for you but it will not include any custom native modules or changes that you have made to your local native projects.
Unless you are sure that you know what you are doing, we recommend aborting the build and doing a native release build through Xcode.

Any idea what’s the difference in logic?

Hey @bluedevil2k6, can you share what the project directory looks like for the project?

Sure. Here it is. Not sure how deep you wanted to see the nested directories.

do you have react-native-unimodules installed as a dependency? you can remove it.

No, it’s not installed (not in package.json nor physically in node_modules).

It’s weird that the build command thinks it’s bare, but other commands (eg. diagnostics) says it’s managed. Even later in the build, it clearly shows that it’s a managed workflow.

Adam & Expo team - any ideas? Just trying to understand what’s triggering the message and the inconsistency. Thanks!

if you have an ios directory or android directory or react-native-unimodules installed then we identify it as a bare project

Don’t have any of that. Strange!

could you possibly share your project? i’m brentvatne on github

here is the function we use to guess the workflow

Not sure what happened here, but the same function on my machine (expo-cli 4.12.1) shows:

function isBareWorkflowProject(projectRoot) {
  const [pkg] = getPackageJsonAndPath(projectRoot);

  if (pkg.dependencies && pkg.dependencies.expokit) {
    return false;

  const xcodeprojFiles = (0, _glob().sync)('ios/**/*.xcodeproj', {
    absolute: true,
    cwd: projectRoot

  if (xcodeprojFiles.length) {
    return true;

  const gradleFiles = (0, _glob().sync)('android/**/*.gradle', {
    absolute: true,
    cwd: projectRoot

  if (gradleFiles.length) {
    return true;

  return false;

Could that make a difference? This looks like ECMAScript reference notation that I’m not too familiar with. Unfortunately this project is private source, but I can see about making a copy of it and still make it reproducible.

Your version looks like it’s a compiled/transpiled version of the original source, so it looks to me like it’s basically the same as the one Brent linked to.

What happens if you edit it to add some console.log() statements? Is it actually being called? Which return statement is being called?

I actually don’t think that’s the right place to look for your issue.

If I have a look on my machine I see this:

$ pwd
$ grep -rl 'currently only supports managed workflow apps' .

The message is coming from utils.js in the following function:

async function maybeBailOnWorkflowWarning({
}) {
  const {
  } = await ProjectUtils().findProjectRootAsync(projectRoot);

  if (workflow === 'managed') {
    return false;

  const command = `expo build:${platform}`;

  _log().default.warn(_chalk().default.bold(`⚠️  ${command} currently only supports managed workflow apps.`));

So workflow is set by calling findProjectRootAsync() which is in ./build/commands/utils/ProjectUtils.js:

async function findProjectRootAsync(base) {
  let previous = null;
  let dir = base;

  do {
    try {
      var _pkg$dependencies, _pkg$dependencies2;

      // This will throw if there is no package.json in the directory
      const pkg = await _jsonFile().default.readAsync(_path().default.join(dir, 'package.json'));
      const hasReactNativeUnimodules = (_pkg$dependencies = pkg.dependencies) === null || _pkg$dependencies === void 0 ? void 0 : _pkg$dependencies.hasOwnProperty('react-native-unimodules');
      const hasExpo = (_pkg$dependencies2 = pkg.dependencies) === null || _pkg$dependencies2 === void 0 ? void 0 : _pkg$dependencies2.hasOwnProperty('expo');
      const isManaged = hasExpo && !hasReactNativeUnimodules;
      const workflow = isManaged ? 'managed' : 'bare';
      return {
        projectRoot: dir,
    } catch {// Expected to throw if no package.json is present
    } finally {
      previous = dir;
      dir = _path().default.dirname(dir);
  } while (dir !== previous);

  throw new (_CommandError().default)('NO_PROJECT', 'No managed or bare projects found. Please make sure you are inside a project folder.');
} // If we get here and can't find expo-updates or package.json we just assume
// that we are not using the old expo-updates

That looks like it tries to find a package.json in your current directory and if it doesn’t find one it keeps searching parent, grandparent, etc. directories until it gets to the root. I assume you do have a package.json in the root of your project, and that it is checked in to Git and not in your .gitignore file.

Once it has a package.json it does some checks on the dependencies. If there are no dependencies, or if one of your dependencies is called “react-native-unimodules” it sets hasReactNativeUnimodules to true.

hasExpo is set to true if one of your dependencies is called “expo”.

If hasExpo is true and also, hasReactNativeUnimodules is false, then isManaged is set to true. Otherwise it’s set to false.

If isManaged is true, then workflow is set to “managed”. Otherwise “bare”.

If I were to guess, you have previously run expo prebuild or expo run:ios and it added react-native-unimodules to your dependencies. Then you deleted the android and ios directories, but did not notice the additional deps.