Este blog publicou 27 artigos desde 19 de agosto. Dezenove deles saíram na mesma janela da noite e nenhum saiu no mesmo segundo: o mais adiantado entrou às 19:18:27, o mais atrasado às 19:25:43. São 7 minutos e 16 segundos de espalhamento, distribuídos em sete minutos diferentes do relógio, para uma tarefa que tem hora marcada e roda sozinha todo dia.
Ninguém mexeu na configuração entre um dia e outro. A hora marcada é um pedido; a execução é um segundo evento, e ele acontece quando a máquina chega nele. Quem agenda post no WordPress convive com a mesma distância, com um detalhe que muda a escala do problema: ali o que empurra a fila não é um relógio, é uma visita ao site. E quando a publicação é o primeiro elo de uma esteira que ainda tem que postar no Instagram e avisar a equipe, o atraso não termina no atraso.
O que os 19 carimbos medem, e o que não medem
Os artigos daqui não passam pelo agendador do WordPress. Uma rotina diária escreve direto na REST API com o status já em publish, então o horário gravado é o instante em que a rotina conseguiu rodar. O espalhamento de sete minutos é do agendador que chama a rotina, não do WP-Cron.
O número serve como piso. Aquele agendador é um cron de verdade: roda em máquina dedicada, não depende de tráfego nenhum, ninguém disputa o horário com ele. Mesmo assim variou sete minutos em dezenove tentativas. O WP-Cron trabalha em condições bem piores, e a variação dele não se mede em minutos, se mede em quando alguém abre uma página do seu site.
Tem um caso mais gritante nesses 27 artigos: um deles não caiu na janela da noite. Saiu às 9:17 da manhã, dez horas antes do horário dos outros dezenove. Não há como saber o que aconteceu naquele dia, e é exatamente esse o ponto. Não existe registro de tentativa, de atraso nem de recuperação em lugar nenhum. Sobrou um carimbo de horário fora do padrão, que só apareceu porque alguém foi contar. É assim que agendamento quebrado se parece por dentro: não como erro, como uma data diferente da combinada.
Onde o WordPress guarda a hora que você marcou
Quando você salva um artigo com data futura, o status dele vira future e o WordPress cria um evento único de cron para aquele post específico. A função responsável é _future_post_hook(), e ela faz duas coisas na ordem: limpa qualquer agendamento anterior daquele post e agenda um novo evento publish_future_post para o timestamp GMT da data que você escolheu.
Na hora marcada, quem atende é check_and_publish_future_post(). Ela confere se o post ainda está em future, compara a data com o momento atual e decide: se o horário ainda não chegou, reagenda o evento e sai sem publicar; se já passou, chama wp_publish_post() e o artigo vai ao ar.
Repare que não existe nenhuma verificação de atraso nessa comparação. O código pergunta se a hora já passou, não quanto tempo faz que passou. Um evento que deveria ter rodado às 9h e só roda às 14h publica o artigo às 14h sem reclamar, e é por isso que a maioria dos atrasos de agendamento passa em branco: do ponto de vista do WordPress, aquilo funcionou.
Quem aperta o gatilho é a visita, não o relógio
O evento está agendado, a função está pronta, e nada disso roda por conta própria. A documentação do WordPress é direta sobre o mecanismo: “WP-Cron works by checking, on every page load, a list of scheduled tasks to see what needs to be run.” É uma checagem por carregamento de página.
Na prática, a função wp_cron() pendura a execução na ação shutdown da requisição. Ou seja: alguém precisa abrir uma página, o PHP precisa processar essa página até o fim, e só então a fila de tarefas é olhada. Sem requisição, a fila fica intacta por quanto tempo for.
A própria documentação dá o exemplo em que isso dói: “Scheduling errors could occur if you schedule a task for 2:00PM and no page loads occur until 5:00PM.” Três horas de atraso, sem erro, sem aviso. Em blog novo, em blog de nicho, em site institucional que recebe visita em horário comercial e silêncio de madrugada, agendar para 6h da manhã é agendar para o primeiro visitante depois das 6h.
Esse é o comportamento padrão, e ele tem remédio oficial. A documentação recomenda desligar o gatilho por visita com define( 'DISABLE_WP_CRON', true ); no wp-config.php e chamar o arquivo de fora, por um cron de sistema, com uma linha do tipo wget --delete-after http://SEU_SITE/wp-cron.php. A partir daí a fila passa a ser olhada em intervalo fixo, independente de quem entrou no site.
O cache que impede o PHP de acordar
Existe um caso pior que o site sem tráfego: o site com tráfego cujas visitas não acordam o PHP. Cache de página entrega HTML pronto do disco ou da memória, sem executar o WordPress. A visita acontece, o visitante é servido, e a ação shutdown do WordPress nunca é alcançada porque o WordPress não rodou.
No fórum oficial existe o relato inteiro desse cenário. O autor do tópico descreve o sintoma com as palavras de quem só vê a tela: “The time/date passes, and I see a red ‘missed schedule’ error.” Nenhum plugin novo, nenhuma mudança de hospedagem, agendamento que funcionava e parou. O suporte pediu para desativar o plugin de cache, o post de teste saiu na hora, e a investigação seguiu para descobrir qual configuração específica de cache de página estava servindo tudo estático.
É um diagnóstico que quase nunca é o primeiro palpite, porque cache e agendamento parecem assuntos de departamentos diferentes. São o mesmo assunto quando o agendador depende de execução de PHP.
O post sai e o envio à rede fica para trás
Até aqui o problema era o artigo ir ao ar. Agora entra o elo seguinte, e ele tem uma falha própria.
Quando o artigo agendado finalmente vira público, wp_publish_post() chama wp_transition_post_status(), que dispara a ação transition_post_status com o status novo, o status antigo e o objeto do post. Um plugin que escuta essa troca recebe o post agendado igual ao post publicado à mão: para ele, a origem do publish não faz diferença. Essa é a razão técnica de o agendamento do WordPress e a publicação automática na rede conseguirem conviver sem gambiarra.
O problema é que o envio à rede raramente acontece dentro dessa mesma requisição, e não deveria: postar no Instagram são duas chamadas HTTP à Graph API, uma para criar o contêiner de mídia em /media e outra para publicar em /media_publish. Segurar o salvamento do post esperando resposta da Meta é pedir para o editor travar. O caminho sensato é agendar o envio para segundos depois, e aí a esteira volta a depender do cron que já se mostrou frágil.
Pior: o wp-cron.php remove o evento da fila antes de executá-lo. Na ordem exata do arquivo, o timestamp é conferido, o evento recorrente é reagendado, wp_unschedule_event() é chamado, e só então do_action_ref_array() roda o gancho. Se o PHP estourar o tempo no meio do envio, o evento já não existe mais. O artigo está publicado, o registro diz que o envio está pendente, e não há nada agendado para chamá-lo de volta.
O que o atraso faz com a janela da Meta
Na maior parte dos casos, atraso de publicação é só um problema de pontualidade. Há dois pontos em que ele passa a quebrar coisa.
O primeiro é o contêiner de mídia. Criar o contêiner e publicá-lo são etapas separadas, e a documentação de publicação de conteúdo do Instagram avisa que o contêiner não publicado em 24 horas expira. Uma esteira que cria o contêiner na hora do publish e tenta publicar depois, num segundo evento de cron que ficou preso, tem esse teto para acordar. Passou de 24 horas, o contêiner morreu e o envio precisa começar de novo.
O segundo é a fila que se acumula. A conta do Instagram aceita 100 publicações por API dentro de uma janela móvel de 24 horas, com carrossel contando como uma. Cem é bastante para um blog; deixa de ser bastante quando quatro dias de agendamento atrasado destravam juntos porque alguém finalmente visitou o site, e a agência que cuida de vários perfis sente isso antes. A janela é móvel, então não zera à meia-noite: ela zera 24 horas depois de cada envio.
Fora da técnica sobra o efeito mais óbvio e o menos discutido. Artigo agendado para as 9h que sai às 14h perde a razão de ter sido agendado para as 9h, e o post na rede sai no horário errado junto. Quem agenda para ganhar horário está comprando exatamente aquilo que o WP-Cron não garante, e esse custo tem uma conta própria que já detalhamos em agendar post no Instagram e o custo que volta toda semana.
A rede de segurança precisa existir nos dois lados
Do lado do WordPress, o remédio é o cron de sistema descrito acima. Ele resolve o atraso de publicação e não resolve o envio que morreu no meio, porque esse envio não está mais agendado para ninguém encontrar.
Do lado do envio, o que funciona é alguém revisitar o estado depois. No Post Ping isso é uma verificação diária que procura artigos publicados que não foram notificados e trata cada um como pendência aberta, em vez de confiar que o evento de cron original deu conta. A detecção da publicação é a mesma descrita acima, pela troca de status, então o artigo agendado entra na esteira sem nenhum passo extra na redação. As duas coisas estão descritas em recursos.
Nenhuma das duas peças substitui a outra. Cron de sistema sem repescagem publica na hora e perde envios; repescagem sem cron de sistema envia tudo, sempre tarde.
Por que você não consegue auditar o atraso depois
Aqui está a parte que surpreende quem vai procurar evidência: ela não existe. A função wp_publish_post() faz um único UPDATE na tabela de posts, e esse update grava um campo só, o post_status. A data do post continua sendo a data que você marcou. O post_modified não é tocado.
Então um artigo agendado para as 9h e publicado de fato às 14h fica registrado como publicado às 9h, para sempre, em todos os lugares que leem esse campo: a lista de posts, o feed, o sitemap, a REST API. Se o agendamento do WordPress fosse o que publica este blog, os 27 carimbos do começo deste artigo não serviriam para medir atraso nenhum: eles marcariam o pedido, não a entrega.
Quem quer o número real precisa medir do lado de fora. O registro de envio às redes tem a hora verdadeira, porque é gravado quando a chamada acontece. Comparar essa hora com a data do post é a única forma de saber se o agendamento está cumprindo o horário, e é uma comparação que vale fazer uma vez por mês em blog que agenda.
Se o ponto de partida for antes disso, e a dúvida for como a publicação sai do WordPress e chega às redes sem ninguém apertar botão, o caminho inteiro está em publicar do WordPress no Instagram e Facebook automaticamente.
