Multisite subsite using TwentyFifteen theme

I have a multisite install with a few subsites using the TwentyFifteen theme. Every time I deploy changes to production, I get the below error.

> The theme directory "twentyfifteen" does not exist.
> ERROR: The themes directory is either empty or doesn’t exist. Please check your installation.

To resolve:

  1. Go to Themes in the dashboard where it says: The active theme is broken. Reverting to the default theme.
  2. Activate a different theme
  3. Reactivate TwentyFifteen

I’m wondering what could cause this behavior on every new deploy.

Related?:

@allurewebsolutions Could you you share the topmost entry in your project’s CHANGELOG.md? This should help us know roughly what version of Trellis you are using.

I’m wondering in particular if you have roots/trellis#720 and its followup roots/trellis#733 (roughly Jan 12, 2017); also the more recent roots/trellis#840 (Jun 14, 2017). These all attempt to prevent a similar issue to what you are reporting.

If your Trellis isn’t as recent as #840 above, could you try updating and see if that resolves the issue? If the problem continues even with the update above, could you deploy again with the -vvv option for verbose output:

ansible-playbook deploy.yml -e env=production -e site=example.com -vvv 

I’m interested to see the verbose output particularly for these two tasks:

  • Get WP theme template root
  • Update WP theme paths

To potentially help me reproduce the issue (I can’t reproduce so far), is the primary site (e.g., domain_current_site) using TwentyFifteen or a custom theme that is included in your repo? Are your subsites using subdomains or subdirectories?

Thank you for your help in advance! @fullyint

Topmost entry:

Forward extra bin/deploy.sh parameters to ansible-playbook (#748)

  • Primary site is using TwentyFifteen

  • Subsites are subdomains

I updated to #841, ran the deploy, and the same issue exists. Results of verbose output:

TASK [deploy : Get WP theme template root] *************************************
task path: /Users/mike/Sites/Development/DO-AllureWebSolutions-1/trellis/roles/deploy/hooks/finalize-after.yml:14
Using module file /usr/local/Cellar/ansible/2.2.1.0_1/libexec/lib/python2.7/site-packages/ansible/modules/core/commands/command.py
<104.236.36.135> ESTABLISH SSH CONNECTION FOR USER: web
<104.236.36.135> SSH: EXEC ssh -o ForwardAgent=yes -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o User=web -o ConnectTimeout=10 -o ControlPath=/Users/mike/.ansible/cp/ansible-ssh-%h-%p-%r 104.236.36.135 '/bin/sh -c '"'"'/usr/bin/python && sleep 0'"'"''
ok: [104.236.36.135] => {
    "changed": false, 
    "cmd": [
        "wp", 
        "option", 
        "get", 
        "template_root", 
        "--skip-plugins"
    ], 
    "delta": "0:00:01.267442", 
    "end": "2017-07-05 18:06:18.040250", 
    "failed": false, 
    "failed_when_result": false, 
    "invocation": {
        "module_args": {
            "_raw_params": "wp option get template_root --skip-plugins", 
            "_uses_shell": false, 
            "chdir": "/srv/www/allureprojects.com/current", 
            "creates": null, 
            "executable": null, 
            "removes": null, 
            "warn": true
        }, 
        "module_name": "command"
    }, 
    "rc": 0, 
    "start": "2017-07-05 18:06:16.772808", 
    "stderr": "", 
    "stdout": "/srv/www/allureprojects.com/releases/20170705180017/web/wp/wp-content/themes", 
    "stdout_lines": [
        "/srv/www/allureprojects.com/releases/20170705180017/web/wp/wp-content/themes"
    ], 
    "warnings": []
}

TASK [deploy : Update WP theme paths] ******************************************
task path: /Users/mike/Sites/Development/DO-AllureWebSolutions-1/trellis/roles/deploy/hooks/finalize-after.yml:22
Using module file /usr/local/Cellar/ansible/2.2.1.0_1/libexec/lib/python2.7/site-packages/ansible/modules/core/commands/command.py
<104.236.36.135> ESTABLISH SSH CONNECTION FOR USER: web
<104.236.36.135> SSH: EXEC ssh -o ForwardAgent=yes -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o User=web -o ConnectTimeout=10 -o ControlPath=/Users/mike/.ansible/cp/ansible-ssh-%h-%p-%r 104.236.36.135 '/bin/sh -c '"'"'/usr/bin/python && sleep 0'"'"''
changed: [104.236.36.135] => (item=stylesheet_root) => {
    "changed": true, 
    "cmd": [
        "wp", 
        "option", 
        "set", 
        "stylesheet_root", 
        "/srv/www/allureprojects.com/releases/20170705180537/web/wp/wp-content/themes"
    ], 
    "delta": "0:00:00.716524", 
    "end": "2017-07-05 18:06:19.339867", 
    "invocation": {
        "module_args": {
            "_raw_params": "wp option set stylesheet_root /srv/www/allureprojects.com/releases/20170705180537/web/wp/wp-content/themes", 
            "_uses_shell": false, 
            "chdir": "/srv/www/allureprojects.com/current", 
            "creates": null, 
            "executable": null, 
            "removes": null, 
            "warn": true
        }, 
        "module_name": "command"
    }, 
    "item": "stylesheet_root", 
    "rc": 0, 
    "start": "2017-07-05 18:06:18.623343", 
    "stderr": "", 
    "stdout": "Success: Updated 'stylesheet_root' option.", 
    "stdout_lines": [
        "Success: Updated 'stylesheet_root' option."
    ], 
    "warnings": []
}
Using module file /usr/local/Cellar/ansible/2.2.1.0_1/libexec/lib/python2.7/site-packages/ansible/modules/core/commands/command.py
<104.236.36.135> ESTABLISH SSH CONNECTION FOR USER: web
<104.236.36.135> SSH: EXEC ssh -o ForwardAgent=yes -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o User=web -o ConnectTimeout=10 -o ControlPath=/Users/mike/.ansible/cp/ansible-ssh-%h-%p-%r 104.236.36.135 '/bin/sh -c '"'"'/usr/bin/python && sleep 0'"'"''
changed: [104.236.36.135] => (item=template_root) => {
    "changed": true, 
    "cmd": [
        "wp", 
        "option", 
        "set", 
        "template_root", 
        "/srv/www/allureprojects.com/releases/20170705180537/web/wp/wp-content/themes"
    ], 
    "delta": "0:00:00.945804", 
    "end": "2017-07-05 18:06:20.604422", 
    "invocation": {
        "module_args": {
            "_raw_params": "wp option set template_root /srv/www/allureprojects.com/releases/20170705180537/web/wp/wp-content/themes", 
            "_uses_shell": false, 
            "chdir": "/srv/www/allureprojects.com/current", 
            "creates": null, 
            "executable": null, 
            "removes": null, 
            "warn": true
        }, 
        "module_name": "command"
    }, 
    "item": "template_root", 
    "rc": 0, 
    "start": "2017-07-05 18:06:19.658618", 
    "stderr": "", 
    "stdout": "Success: Updated 'template_root' option.", 
    "stdout_lines": [
        "Success: Updated 'template_root' option."
    ], 
    "warnings": []
}
1 Like

@fullyint sorry to bother you again, but I was hoping to see if the verbose output provided any insight to what might be going on.

@allurewebsolutions Thank you for the excellent set of details about this issue. I’m surprised to not remember anyone ever raising the issue before.

I’ve proposed a fix in https://github.com/roots/trellis/pull/848
If you’re inclined to test, that would be wonderful!

@fullyint You are awesome!!! It worked perfectly :slight_smile:

> TASK [deploy : Update WP theme paths] ******************************************
> changed: [104.236.36.135] => (item=[u'http://allureprojects.com/wp/ /srv/www/allureprojects.com/releases/20170714141226/web/wp/wp-content/themes', u'template_root'])
> changed: [104.236.36.135] => (item=[u'http://allureprojects.com/wp/ /srv/www/allureprojects.com/releases/20170714141226/web/wp/wp-content/themes', u'stylesheet_root'])
> skipping: [104.236.36.135] => (item=[u'http://potterylovely.com/ /themes', u'template_root']) 
> skipping: [104.236.36.135] => (item=[u'http://potterylovely.com/ /themes', u'stylesheet_root']) 
> skipping: [104.236.36.135] => (item=[u'http://juliapignata.com/ http://mikedoubintchik.com/ http://4djustice.com/ /themes', u'template_root']) 
> skipping: [104.236.36.135] => (item=[u'http://juliapignata.com/ http://mikedoubintchik.com/ http://4djustice.com/ /themes', u'stylesheet_root']) 
> skipping: [104.236.36.135] => (item=[u'http://bestchefcatering.com/ http://benjaminvwilliams.com/ /themes', u'template_root']) 
> skipping: [104.236.36.135] => (item=[u'http://bestchefcatering.com/ http://benjaminvwilliams.com/ /themes', u'stylesheet_root']) 
> skipping: [104.236.36.135] => (item=[u'http://vc-multilanguage.allureprojects.com/ /themes', u'template_root']) 
> skipping: [104.236.36.135] => (item=[u'http://vc-multilanguage.allureprojects.com/ /themes', u'stylesheet_root']) 
> skipping: [104.236.36.135] => (item=[u'http://trianglecasineros.com/ /themes', u'template_root']) 
> skipping: [104.236.36.135] => (item=[u'http://trianglecasineros.com/ /themes', u'stylesheet_root']) 
> changed: [104.236.36.135] => (item=[u'http://allure-gallery.allureprojects.com/ /srv/www/allureprojects.com/releases/20170705180537/web/wp/wp-content/themes', u'template_root'])
> changed: [104.236.36.135] => (item=[u'http://allure-gallery.allureprojects.com/ /srv/www/allureprojects.com/releases/20170705180537/web/wp/wp-content/themes', u'stylesheet_root'])
> changed: [104.236.36.135] => (item=[u'http://wp-post-modal.allureprojects.com/ /srv/www/allureprojects.com/releases/20170705180537/web/wp/wp-content/themes', u'template_root'])
> changed: [104.236.36.135] => (item=[u'http://wp-post-modal.allureprojects.com/ /srv/www/allureprojects.com/releases/20170705180537/web/wp/wp-content/themes', u'stylesheet_root'])
> skipping: [104.236.36.135] => (item=[u'http://bedrock-sage-starter.allureprojects.com/ /themes', u'template_root']) 
> skipping: [104.236.36.135] => (item=[u'http://bedrock-sage-starter.allureprojects.com/ /themes', u'stylesheet_root'])
1 Like

I’ve encountered the same problem as @allurewebsolutions after successfully installing Multisite (thanks again, @fullyint).

I have a fresh clone of Trellis and Bedrock. I’ve tried out the changes in PR 848 and in PR 854 too (a regex seems to be the difference). So I’ve actually replaced finalize_after.yml with this, but unfortunately it did not solve the issue, I get the error messages when visiting the site:

The theme directory "twentyseventeen" does not exist.
ERROR: The themes directory is either empty or doesn’t exist. Please check your installation.

Although when I just visit the admin area, that fixes the problem instantly, and the default twentyseventeen theme works again on the sites.

Even with -vvv, nothing happens at the “Update WP theme paths” task, nothing can be seen in the output, it’s empty. For the task “Get WP theme template and stylesheet roots” I get this (and after that the empty task result can be seen too):

TASK [deploy : Get WP theme template and stylesheet roots] *****************************************************
task path: /home/vagrant/trellis/roles/deploy/hooks/finalize-after.yml:14
Using module file /usr/local/lib/python2.7/dist-packages/ansible/modules/commands/command.py
<sitename.tld> ESTABLISH SSH CONNECTION FOR USER: web
<sitename.tld> SSH: EXEC ssh -o ForwardAgent=yes -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o User=web -o ConnectTimeout=10 -o ControlPath=/home/vagrant/.ansible/cp/f59e68137f sitename.tld '/bin/sh -c '"'"'/usr/bin/python && sleep 0'"'"''
<sitename.tld> (0, '\n{"changed": true, "end": "2017-09-17 02:36:49.358077", "stdout": "http://sitename.tld/wp/ \\nhttp://sub1.sitename.tld/wp/ ", "cmd": " wp site list --field=url | xargs -I {} bash -c \'export url=\\"{}\\"; echo -n \\"$url \\" && wp option get template_root --skip-plugins --url=$url || echo\'\\n ", "rc": 0, "start": "2017-09-17 02:36:48.377430", "stderr": "", "delta": "0:00:00.980647", "invocation": {"module_args": {"warn": true, "executable": null, "chdir": "/srv/www/sitename.tld/current", "_raw_params": " wp site list --field=url | xargs -I {} bash -c \'export url=\\"{}\\"; echo -n \\"$url \\" && wp option get template_root --skip-plugins --url=$url || echo\'\\n ", "removes": null, "creates": null, "_uses_shell": true}}, "warnings": []}\n', '')
ok: [sitename.tld] => (item=template_root) => {
    "changed": false,
    "cmd": " wp site list --field=url | xargs -I {} bash -c 'export url=\"{}\"; echo -n \"$url \" && wp option get template_root --skip-plugins --url=$url || echo'\n ",
    "delta": "0:00:00.980647",
    "end": "2017-09-17 02:36:49.358077",
    "failed": false,
    "failed_when_result": false,
    "invocation": {
        "module_args": {
            "_raw_params": " wp site list --field=url | xargs -I {} bash -c 'export url=\"{}\"; echo -n \"$url \" && wp option get template_root --skip-plugins --url=$url || echo'\n ",
            "_uses_shell": true,
            "chdir": "/srv/www/sitename.tld/current",
            "creates": null,
            "executable": null,
            "removes": null,
            "warn": true
        }
    },
    "item": "template_root",
    "rc": 0,
    "start": "2017-09-17 02:36:48.377430",
    "stderr": "",
    "stderr_lines": [],
    "stdout": "http://sitename.tld/wp/ \nhttp://sub1.sitename.tld/wp/ ",
    "stdout_lines": [
        "http://sitename.tld/wp/ ",
        "http://sub1.sitename.tld/wp/ "
    ]
}
Using module file /usr/local/lib/python2.7/dist-packages/ansible/modules/commands/command.py
<sitename.tld> ESTABLISH SSH CONNECTION FOR USER: web
<sitename.tld> SSH: EXEC ssh -o ForwardAgent=yes -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o User=web -o ConnectTimeout=10 -o ControlPath=/home/vagrant/.ansible/cp/f59e68137f sitename.tld '/bin/sh -c '"'"'/usr/bin/python && sleep 0'"'"''
<sitename.tld> (0, '\n{"changed": true, "end": "2017-09-17 02:36:50.565836", "stdout": "http://sitename.tld/wp/ \\nhttp://sub1.sitename.tld/wp/ ", "cmd": " wp site list --field=url | xargs -I {} bash -c \'export url=\\"{}\\"; echo -n \\"$url \\" && wp option get stylesheet_root --skip-plugins --url=$url || echo\'\\n ", "rc": 0, "start": "2017-09-17 02:36:49.640575", "stderr": "", "delta": "0:00:00.925261", "invocation": {"module_args": {"warn": true, "executable": null, "chdir": "/srv/www/sitename.tld/current", "_raw_params": " wp site list --field=url | xargs -I {} bash -c \'export url=\\"{}\\"; echo -n \\"$url \\" && wp option get stylesheet_root --skip-plugins --url=$url || echo\'\\n ", "removes": null, "creates": null, "_uses_shell": true}}, "warnings": []}\n', '')
ok: [sitename.tld] => (item=stylesheet_root) => {
    "changed": false,
    "cmd": " wp site list --field=url | xargs -I {} bash -c 'export url=\"{}\"; echo -n \"$url \" && wp option get stylesheet_root --skip-plugins --url=$url || echo'\n ",
    "delta": "0:00:00.925261",
    "end": "2017-09-17 02:36:50.565836",
    "failed": false,
    "failed_when_result": false,
    "invocation": {
        "module_args": {
            "_raw_params": " wp site list --field=url | xargs -I {} bash -c 'export url=\"{}\"; echo -n \"$url \" && wp option get stylesheet_root --skip-plugins --url=$url || echo'\n ",
            "_uses_shell": true,
            "chdir": "/srv/www/sitename.tld/current",
            "creates": null,
            "executable": null,
            "removes": null,
            "warn": true
        }
    },
    "item": "stylesheet_root",
    "rc": 0,
    "start": "2017-09-17 02:36:49.640575",
    "stderr": "",
    "stderr_lines": [],
    "stdout": "http://sitename.tld/wp/ \nhttp://sub1.sitename.tld/wp/ ",
    "stdout_lines": [
        "http://sitename.tld/wp/ ",
        "http://sub1.sitename.tld/wp/ "
    ]
}

TASK [deploy : Update WP theme paths] **************************************************************************
task path: /home/vagrant/trellis/roles/deploy/hooks/finalize-after.yml:31

TASK [deploy : Reload php-fpm] *********************************************************************************

What am I doing wrong?

@iamkarex Thanks for the thorough details.

Try this

Could you try these steps (still with the e58a66c version)?

  • for each affected (sub)site
    • use the wp-admin to activate a different theme
    • reactivate twentyseventeen theme
  • run deploy.yml again and see if it works as expected

If it still fails, could you try this? (untested)

# connect to server
$ ssh web@sitename.tld

# cd to site dir
$ cd /srv/www/sitename.tld

# display active release directory
$ ls -l current
current -> /srv/www/sitename.tld/releases/20170916053410

# cd to current
$ cd current

# assuming you want all subsites to use twentyseventeen
# set template_root option to release dir output above
$ wp site list --field=url | xargs -n1 -I % wp --url=% option set template_root /srv/www/sitename.tld/releases/20170916053410/web/wp/wp-content/themes

# set stylesheet_root option to release dir output above
$ wp site list --field=url | xargs -n1 -I % wp --url=% option set stylesheet_root /srv/www/sitename.tld/releases/20170916053410/web/wp/wp-content/themes

$ exit

# retry deploy on local machine

Here’s why

Indeed, that’s a problem. That task’s with_items is empty in the output above because it is trying to select from the prior task the stdout_lines that match the release directory pattern. The problem in the output above is that for some reason your template_root and stylesheet_root don’t match a pattern like this: /srv/www/sitename.tld/releases/20170916053410. Instead, your values:

    "stdout_lines": [
        "http://sitename.tld/wp/ ",
        "http://sub1.sitename.tld/wp/ "
    ]

The point of the “Try this” steps above was to restore the targeted pattern to your template_root and stylesheet_root. I don’t think you would ever need to do these steps again. I think those strange values were a side effect of trial and error. For example, if you were to wp db reset then wp core multisite-install, then activate your desired themes, I think you wouldn’t ever see the deploy errors above.

If this succeeds, your data point can help get roots/trellis#854 merged.

@fullyint activating another theme simply solved the problem. After the activation of another theme than the default, the subsite works after deploy. Thanks! Yeah, I haven’t changed themes on this deploy before.

I observed one thing though. After Update WP database and before Update WP theme paths there is “a some second window” where Wordpress can’t find the directory (for me it was at least 5, which is not comfortable at all). Can’t we change the order of the tasks in finalize_after.yml to mitigate? Or is there maybe another way to overcome this?

Thanks for implementing the task-reordering, @fullyint!

I’ve tried it out, and – hm, well, I know it seems like I’m obsessed, but I’m afraid that – there might be still a little disruption while deploying as I observed.

Still thinking about a solution I’ve tried out a lot of different things, and came to a possible solution (not fully implemented yet). It might be a bit uncommon, so coming here to ask an expert’s opinion. What if:

  • we create a small plugin that deletes the theme_root site transient on plugins_loaded, but only if a special transient is set (so forcing the expensive search_theme_directories to run on every page load without the transient);
  • we set that special “deploy_in_progress” kinda transient with WP CLI while deploying before the symlink change happens (hence “activating” that plugin);
  • after the deployment is finished, we delete the “deploy_in_progress” transient (or if something goes really wrong, then it still expires after a while).

What do you think about this concept?

1 Like

You’re obsessed.

But some things are worth obsessing about, like a site that doesn’t load.

Details welcome. Roughly how long per each of 5-10 trials (if the site can take it)? Roughly how many subsites in this multisite install?

I’m not a WP expert and haven’t spent much time reconsidering the issue after your post, but here are initial reactions.

Workaround for zero downtime – Move theme

You probably want a fix, not a workaround, but I think you’d avoid downtime :dizzy: completely :dizzy: by copying your TwentyFifteen theme to Bedrock’s web/app/themes (probably have to rename the theme?, likely have to reactivate the “new” theme?). Then the theme’s template_root and stylesheet_root wouldn’t include the problematic deploy release path. Maybe your case has reasons this isn’t as simple as it sounds.

Lessening downtime – Delete transients before symlink change

Slightly simpler than creating, activating and deactivating a plugin, as you suggested, might be to just delete the relevant transients right BEFORE the symlink is switched. There’s still possibility that users’ page loads would recreate the old transients before the symlink is changed, but nonetheless this technique would probably diminish the window during which the site doesn’t load, on average across deploys.

To do this, you could define a deploy hook like the following in your group_vars/all/main.yml (or wherever you want):

deploy_finalize_before:
  - "{{ playbook_dir }}/roles/deploy/hooks/finalize-before.yml"
  - "{{ playbook_dir }}/deploy-hooks/clear-wp-theme-paths.yml"

Then create deploy-hooks/clear-wp-theme-paths.yml with a task like this but that runs wp option delete instead of wp option get. You probably also want this exact task in there too, but add its block condition to its when, making it look like this.

Activate/deactivate plugin – Your idea

If you can’t move the theme location and want to pursue the plugin approach, I think you could add an include file to the deploy_finalize_before hook, like demonstrated above. But maybe instead of toggling the plugin on/off via a transient, you could have the include file run a task to wp plugin activate your plugin. Then create another include file to run right after the symlink is switched, with wp plugin deactivate:

deploy_finalize_after:
  - "{{ playbook_dir }}/deploy-hooks/deactivate-plugin-x.yml"
  - "{{ playbook_dir }}/roles/deploy/hooks/finalize-after.yml"

The stuff above doesn’t include every step, but you can work it out if you dig in. Not being a WP pro, I don’t know if there are wp-cli command alternatives that could handle the issue more efficiently, like just disabling given transients temporarily, or something clever.

3 Likes

Thanks for the regiment of ideas!

Some details about my case: I’m just evaluating Trellis now, and I have created some test environments on remote servers (that’s why e.g. I have twentyfifteen). Currently 2 subsites, everything stock, and I observe 1 to 10 secs template path error per deploy currently (I don’t really know why sometimes 1, sometimes 10). (And I plan to have a ~5 subsite Multisite install with a custom theme and many users and probably frequent deploys, that’s why I’m really interested in this question.)

For someone using stock themes, 1) maybe a great solution (but this I think rarely fits anyone who plan to deploy much).

I thought already about deleting transients before deploy as well, and tried it out, but it did not really work out, as – if I did that right – there’s still some time when theme paths would fail.

To clarify my idea a little bit, I actually thought about a Must-use mu-plugin, this way the transient toggle in a hook makes a little bit more sense I think. (This way actually this concept may become part of Trellis one time.) I know that this is not really a standard way of doing things, but on the other hand I like the idea of “informing” WP through the database that a deploy is in progress.

So I’d consider the plugin approach a solution to the problem – if it’d work –, and I’ll try to implement in my case. An ultimate solution would be to rewrite search_theme_directories in WP core and be done with it, but this is out of scope here.

2 Likes

If you plan to have a custom theme, I imagine that means you’ll commit the theme to a Bedrock-based project, to the web/app/themes directory. If so, you would be using the “Workaround for zero downtime – Move theme” approach.

I couldn’t understand for sure, but I think the quote above suggests that you’ve disqualified option #1 (“Workaround for zero downtime – Move theme” ). I was confused because I think you’ve indicated that you’ll be using option 1.

When I wrote in option 1 that “you’d avoid downtime completely by copying your TwentyFifteen theme to Bedrock’s web/app/themes,” I didn’t state it explicitly, but I meant that the project (repo) you’d be deploying so often would include your custom theme, whether truly custom or just a plain copy of TwentyFifteen.

Just to be clear, I didn’t mean that you’d customize the deploy process to every time copy the TwentyFifiteen theme from web/wp/wp-content/themes to web/app/themes. Rather, being committed to your repo, the theme would automatically be deployed to web/app/themes.

I’m writing all this because I couldn’t quite tell but I think you figured the “Workaround for zero downtime – Move theme” approach wouldn’t work for you, but it appears to me that this is the approach you’ll end up with anyway, given that you plan to build/deploy a custom theme.

If you plan to create a custom theme, use a basic custom theme in your test environments while you evaluate Trellis. It’s the WP core twentyfifteen themes from web/wp/wp-content/themes that will have the problem in discussion, but your future custom theme should not have the problem.