diff --git a/cfg.c b/cfg.c
index bc6eff72..b0ea5d88 100644
--- a/cfg.c
+++ b/cfg.c
@@ -28,14 +28,25 @@
 #include "tmux.h"
 
 char			 *cfg_file;
-static struct cmd_q	 *cfg_cmd_q;
 int			  cfg_finished;
-int			  cfg_references;
 static char		**cfg_causes;
 static u_int		  cfg_ncauses;
 struct client		 *cfg_client;
 
-static void	  cfg_default_done(struct cmd_q *);
+static enum cmd_retval
+cfg_done(__unused struct cmd_q *cmdq, __unused void *data)
+{
+	if (cfg_finished)
+		return (CMD_RETURN_NORMAL);
+	cfg_finished = 1;
+
+	if (!RB_EMPTY(&sessions))
+		cfg_show_causes(RB_MIN(sessions, &sessions));
+
+	if (cfg_client != NULL)
+		server_client_unref(cfg_client);
+	return (CMD_RETURN_NORMAL);
+}
 
 void
 set_cfg_file(const char *path)
@@ -50,30 +61,24 @@ start_cfg(void)
 	const char	*home;
 	int		 quiet = 0;
 
-	cfg_cmd_q = cmdq_new(NULL);
-	cfg_cmd_q->emptyfn = cfg_default_done;
-
-	cfg_finished = 0;
-	cfg_references = 1;
-
 	cfg_client = TAILQ_FIRST(&clients);
 	if (cfg_client != NULL)
 		cfg_client->references++;
 
-	load_cfg(TMUX_CONF, cfg_cmd_q, 1);
+	load_cfg(TMUX_CONF, cfg_client, NULL, 1);
 
 	if (cfg_file == NULL && (home = find_home()) != NULL) {
 		xasprintf(&cfg_file, "%s/.tmux.conf", home);
 		quiet = 1;
 	}
 	if (cfg_file != NULL)
-		load_cfg(cfg_file, cfg_cmd_q, quiet);
+		load_cfg(cfg_file, cfg_client, NULL, quiet);
 
-	cmdq_continue(cfg_cmd_q);
+	cmdq_append(cfg_client, cmdq_get_callback(cfg_done, NULL));
 }
 
 int
-load_cfg(const char *path, struct cmd_q *cmdq, int quiet)
+load_cfg(const char *path, struct client *c, struct cmd_q *cmdq, int quiet)
 {
 	FILE		*f;
 	char		 delim[3] = { '\\', '\\', '\0' };
@@ -81,6 +86,7 @@ load_cfg(const char *path, struct cmd_q *cmdq, int quiet)
 	size_t		 line = 0;
 	char		*buf, *cause1, *p;
 	struct cmd_list	*cmdlist;
+	struct cmd_q	*new_cmdq;
 
 	log_debug("loading %s", path);
 	if ((f = fopen(path, "rb")) == NULL) {
@@ -116,8 +122,13 @@ load_cfg(const char *path, struct cmd_q *cmdq, int quiet)
 
 		if (cmdlist == NULL)
 			continue;
-		cmdq_append(cmdq, cmdlist, NULL);
+		new_cmdq = cmdq_get_command(cmdlist, NULL, NULL, 0);
+		if (cmdq != NULL)
+			cmdq_insert_after(cmdq, new_cmdq);
+		else
+			cmdq_append(c, new_cmdq);
 		cmd_list_free(cmdlist);
+
 		found++;
 	}
 	fclose(f);
@@ -125,37 +136,6 @@ load_cfg(const char *path, struct cmd_q *cmdq, int quiet)
 	return (found);
 }
 
-static void
-cfg_default_done(__unused struct cmd_q *cmdq)
-{
-	log_debug("%s: %u references%s", __func__, cfg_references,
-	    cfg_finished ? " (finished)" : "");
-
-	if (cfg_finished || --cfg_references != 0)
-		return;
-	cfg_finished = 1;
-
-	if (!RB_EMPTY(&sessions))
-		cfg_show_causes(RB_MIN(sessions, &sessions));
-
-	cmdq_free(cfg_cmd_q);
-	cfg_cmd_q = NULL;
-
-	if (cfg_client != NULL) {
-		/*
-		 * The client command queue starts with client_exit set to 1 so
-		 * only continue if not empty (that is, we have been delayed
-		 * during configuration parsing for long enough that the
-		 * MSG_COMMAND has arrived), else the client will exit before
-		 * the MSG_COMMAND which might tell it not to.
-		 */
-		if (!TAILQ_EMPTY(&cfg_client->cmdq->queue))
-			cmdq_continue(cfg_client->cmdq);
-		server_client_unref(cfg_client);
-		cfg_client = NULL;
-	}
-}
-
 void
 cfg_add_cause(const char *fmt, ...)
 {
diff --git a/cmd-attach-session.c b/cmd-attach-session.c
index daab428f..f0e860f9 100644
--- a/cmd-attach-session.c
+++ b/cmd-attach-session.c
@@ -145,7 +145,7 @@ cmd_attach_session(struct cmd_q *cmdq, int dflag, int rflag, const char *cflag,
 		if (~c->flags & CLIENT_CONTROL)
 			proc_send(c->peer, MSG_READY, -1, NULL, 0);
 		hooks_run(c->session->hooks, c, NULL, "client-attached");
-		cmdq->client_exit = 0;
+		c->flags |= CLIENT_ATTACHED;
 	}
 	recalculate_sizes();
 	alerts_check_session(s);
diff --git a/cmd-command-prompt.c b/cmd-command-prompt.c
index 12ecb493..5e21b2bc 100644
--- a/cmd-command-prompt.c
+++ b/cmd-command-prompt.c
@@ -121,12 +121,24 @@ cmd_command_prompt_exec(struct cmd *self, struct cmd_q *cmdq)
 	return (CMD_RETURN_NORMAL);
 }
 
+static enum cmd_retval
+cmd_command_prompt_error(struct cmd_q *cmdq, void *data)
+{
+	char	*error = data;
+
+	cmdq_error(cmdq, "%s", error);
+	free(error);
+
+	return (CMD_RETURN_NORMAL);
+}
+
 static int
 cmd_command_prompt_callback(void *data, const char *s)
 {
 	struct cmd_command_prompt_cdata	*cdata = data;
 	struct client			*c = cdata->c;
 	struct cmd_list			*cmdlist;
+	struct cmd_q			*new_cmdq;
 	char				*cause, *new_template, *prompt, *ptr;
 	char				*input = NULL;
 
@@ -153,17 +165,19 @@ cmd_command_prompt_callback(void *data, const char *s)
 
 	if (cmd_string_parse(new_template, &cmdlist, NULL, 0, &cause) != 0) {
 		if (cause != NULL) {
-			*cause = toupper((u_char) *cause);
-			status_message_set(c, "%s", cause);
-			free(cause);
-		}
-		return (0);
+			new_cmdq = cmdq_get_callback(cmd_command_prompt_error,
+			    cause);
+		} else
+			new_cmdq = NULL;
+	} else {
+		new_cmdq = cmdq_get_command(cmdlist, NULL, NULL, 0);
+		cmd_list_free(cmdlist);
 	}
 
-	cmdq_run(c->cmdq, cmdlist, NULL);
-	cmd_list_free(cmdlist);
+	if (new_cmdq != NULL)
+		cmdq_append(c, new_cmdq);
 
-	if (c->prompt_callbackfn != (void *) &cmd_command_prompt_callback)
+	if (c->prompt_callbackfn != (void *)&cmd_command_prompt_callback)
 		return (1);
 	return (0);
 }
diff --git a/cmd-confirm-before.c b/cmd-confirm-before.c
index 4241aefb..2dd52c81 100644
--- a/cmd-confirm-before.c
+++ b/cmd-confirm-before.c
@@ -83,12 +83,24 @@ cmd_confirm_before_exec(struct cmd *self, struct cmd_q *cmdq)
 	return (CMD_RETURN_NORMAL);
 }
 
+static enum cmd_retval
+cmd_confirm_before_error(struct cmd_q *cmdq, void *data)
+{
+	char	*error = data;
+
+	cmdq_error(cmdq, "%s", error);
+	free(error);
+
+	return (CMD_RETURN_NORMAL);
+}
+
 static int
 cmd_confirm_before_callback(void *data, const char *s)
 {
 	struct cmd_confirm_before_data	*cdata = data;
 	struct client			*c = cdata->client;
 	struct cmd_list			*cmdlist;
+	struct cmd_q			*new_cmdq;
 	char				*cause;
 
 	if (c->flags & CLIENT_DEAD)
@@ -101,14 +113,17 @@ cmd_confirm_before_callback(void *data, const char *s)
 
 	if (cmd_string_parse(cdata->cmd, &cmdlist, NULL, 0, &cause) != 0) {
 		if (cause != NULL) {
-			cmdq_error(c->cmdq, "%s", cause);
-			free(cause);
-		}
-		return (0);
+			new_cmdq = cmdq_get_callback(cmd_confirm_before_error,
+			    cause);
+		} else
+			new_cmdq = NULL;
+	} else {
+		new_cmdq = cmdq_get_command(cmdlist, NULL, NULL, 0);
+		cmd_list_free(cmdlist);
 	}
 
-	cmdq_run(c->cmdq, cmdlist, NULL);
-	cmd_list_free(cmdlist);
+	if (new_cmdq != NULL)
+		cmdq_append(c, new_cmdq);
 
 	return (0);
 }
diff --git a/cmd-copy-mode.c b/cmd-copy-mode.c
index 4591a37d..dc880d56 100644
--- a/cmd-copy-mode.c
+++ b/cmd-copy-mode.c
@@ -61,7 +61,7 @@ cmd_copy_mode_exec(struct cmd *self, struct cmd_q *cmdq)
 	struct window_pane	*wp = cmdq->state.tflag.wp;
 
 	if (args_has(args, 'M')) {
-		if ((wp = cmd_mouse_pane(&cmdq->item->mouse, &s, NULL)) == NULL)
+		if ((wp = cmd_mouse_pane(&cmdq->mouse, &s, NULL)) == NULL)
 			return (CMD_RETURN_NORMAL);
 		if (c == NULL || c->session != s)
 			return (CMD_RETURN_NORMAL);
@@ -80,7 +80,7 @@ cmd_copy_mode_exec(struct cmd *self, struct cmd_q *cmdq)
 	if (args_has(args, 'M')) {
 		if (wp->mode != NULL && wp->mode != &window_copy_mode)
 			return (CMD_RETURN_NORMAL);
-		window_copy_start_drag(c, &cmdq->item->mouse);
+		window_copy_start_drag(c, &cmdq->mouse);
 	}
 	if (wp->mode == &window_copy_mode && args_has(self->args, 'u'))
 		window_copy_pageup(wp, 0);
diff --git a/cmd-display-panes.c b/cmd-display-panes.c
index 2edb2eb3..471bec02 100644
--- a/cmd-display-panes.c
+++ b/cmd-display-panes.c
@@ -65,32 +65,48 @@ cmd_display_panes_exec(struct cmd *self, struct cmd_q *cmdq)
 	return (CMD_RETURN_NORMAL);
 }
 
+static enum cmd_retval
+cmd_display_panes_error(struct cmd_q *cmdq, void *data)
+{
+	char	*error = data;
+
+	cmdq_error(cmdq, "%s", error);
+	free(error);
+
+	return (CMD_RETURN_NORMAL);
+}
+
 static void
 cmd_display_panes_callback(struct client *c, struct window_pane *wp)
 {
 	struct cmd_list	*cmdlist;
+	struct cmd_q	*new_cmdq;
 	char		*template, *cmd, *expanded, *cause;
 
 	template = c->identify_callback_data;
-	if (wp != NULL) {
-		xasprintf(&expanded, "%%%u", wp->id);
-		cmd = cmd_template_replace(template, expanded, 1);
+	if (wp == NULL)
+		goto out;
+	xasprintf(&expanded, "%%%u", wp->id);
+	cmd = cmd_template_replace(template, expanded, 1);
 
-		if (cmd_string_parse(cmd, &cmdlist, NULL, 0, &cause) != 0) {
-			if (cause != NULL) {
-				*cause = toupper((u_char) *cause);
-				status_message_set(c, "%s", cause);
-				free(cause);
-			}
-		} else {
-			cmdq_run(c->cmdq, cmdlist, NULL);
-			cmd_list_free(cmdlist);
-		}
-
-		free(cmd);
-		free(expanded);
+	if (cmd_string_parse(cmd, &cmdlist, NULL, 0, &cause) != 0) {
+		if (cause != NULL) {
+			new_cmdq = cmdq_get_callback(cmd_display_panes_error,
+			    cause);
+		} else
+			new_cmdq = NULL;
+	} else {
+		new_cmdq = cmdq_get_command(cmdlist, NULL, NULL, 0);
+		cmd_list_free(cmdlist);
 	}
 
+	if (new_cmdq != NULL)
+		cmdq_append(c, new_cmdq);
+
+	free(cmd);
+	free(expanded);
+
+out:
 	free(c->identify_callback_data);
 	c->identify_callback_data = NULL;
 	c->identify_callback = NULL;
diff --git a/cmd-find.c b/cmd-find.c
index 6bf40040..dc4cd889 100644
--- a/cmd-find.c
+++ b/cmd-find.c
@@ -1005,7 +1005,7 @@ cmd_find_target(struct cmd_find_state *fs, struct cmd_find_state *current,
 
 	/* Mouse target is a plain = or {mouse}. */
 	if (strcmp(target, "=") == 0 || strcmp(target, "{mouse}") == 0) {
-		m = &cmdq->item->mouse;
+		m = &cmdq->mouse;
 		switch (type) {
 		case CMD_FIND_PANE:
 			fs->wp = cmd_mouse_pane(m, &fs->s, &fs->wl);
diff --git a/cmd-if-shell.c b/cmd-if-shell.c
index 2a8cbff2..7192b204 100644
--- a/cmd-if-shell.c
+++ b/cmd-if-shell.c
@@ -31,9 +31,9 @@
 
 static enum cmd_retval	 cmd_if_shell_exec(struct cmd *, struct cmd_q *);
 
-static void	cmd_if_shell_callback(struct job *);
-static void	cmd_if_shell_done(struct cmd_q *);
-static void	cmd_if_shell_free(void *);
+static enum cmd_retval	 cmd_if_shell_error(struct cmd_q *, void *);
+static void		 cmd_if_shell_callback(struct job *);
+static void		 cmd_if_shell_free(void *);
 
 const struct cmd_entry cmd_if_shell_entry = {
 	.name = "if-shell",
@@ -56,11 +56,9 @@ struct cmd_if_shell_data {
 	char			*cmd_if;
 	char			*cmd_else;
 
+	struct client		*client;
 	struct cmd_q		*cmdq;
 	struct mouse_event	 mouse;
-
-	int			 bflag;
-	int			 references;
 };
 
 static enum cmd_retval
@@ -70,6 +68,7 @@ cmd_if_shell_exec(struct cmd *self, struct cmd_q *cmdq)
 	struct cmd_if_shell_data	*cdata;
 	char				*shellcmd, *cmd, *cause;
 	struct cmd_list			*cmdlist;
+	struct cmd_q			*new_cmdq;
 	struct session			*s = cmdq->state.tflag.s;
 	struct winlink			*wl = cmdq->state.tflag.wl;
 	struct window_pane		*wp = cmdq->state.tflag.wp;
@@ -104,7 +103,8 @@ cmd_if_shell_exec(struct cmd *self, struct cmd_q *cmdq)
 			}
 			return (CMD_RETURN_ERROR);
 		}
-		cmdq_run(cmdq, cmdlist, &cmdq->item->mouse);
+		new_cmdq = cmdq_get_command(cmdlist, NULL, &cmdq->mouse, 0);
+		cmdq_insert_after(cmdq, new_cmdq);
 		cmd_list_free(cmdlist);
 		return (CMD_RETURN_NORMAL);
 	}
@@ -121,92 +121,80 @@ cmd_if_shell_exec(struct cmd *self, struct cmd_q *cmdq)
 	else
 		cdata->cmd_else = NULL;
 
-	cdata->bflag = args_has(args, 'b');
+	cdata->client = cmdq->client;
+	cdata->client->references++;
 
-	cdata->cmdq = cmdq;
-	memcpy(&cdata->mouse, &cmdq->item->mouse, sizeof cdata->mouse);
-	cmdq->references++;
+	if (!args_has(args, 'b'))
+		cdata->cmdq = cmdq;
+	else
+		cdata->cmdq = NULL;
+	memcpy(&cdata->mouse, &cmdq->mouse, sizeof cdata->mouse);
 
-	cdata->references = 1;
 	job_run(shellcmd, s, cwd, cmd_if_shell_callback, cmd_if_shell_free,
 	    cdata);
 	free(shellcmd);
 
-	if (cdata->bflag)
+	if (args_has(args, 'b'))
 		return (CMD_RETURN_NORMAL);
 	return (CMD_RETURN_WAIT);
 }
 
+static enum cmd_retval
+cmd_if_shell_error(struct cmd_q *cmdq, void *data)
+{
+	char	*error = data;
+
+	cmdq_error(cmdq, "%s", error);
+	free(error);
+
+	return (CMD_RETURN_NORMAL);
+}
+
 static void
 cmd_if_shell_callback(struct job *job)
 {
 	struct cmd_if_shell_data	*cdata = job->data;
-	struct cmd_q			*cmdq = cdata->cmdq, *cmdq1;
+	struct client			*c = cdata->client;
 	struct cmd_list			*cmdlist;
-	char				*cause, *cmd;
-
-	if (cmdq->flags & CMD_Q_DEAD)
-		return;
+	struct cmd_q			*new_cmdq;
+	char				*cause, *cmd, *file = cdata->file;
+	u_int				 line = cdata->line;
 
 	if (!WIFEXITED(job->status) || WEXITSTATUS(job->status) != 0)
 		cmd = cdata->cmd_else;
 	else
 		cmd = cdata->cmd_if;
 	if (cmd == NULL)
-		return;
+		goto out;
 
-	if (cmd_string_parse(cmd, &cmdlist, cdata->file, cdata->line,
-	    &cause) != 0) {
-		if (cause != NULL) {
-			cmdq_error(cmdq, "%s", cause);
-			free(cause);
-		}
-		return;
+	if (cmd_string_parse(cmd, &cmdlist, file, line, &cause) != 0) {
+		if (cause != NULL)
+			new_cmdq = cmdq_get_callback(cmd_if_shell_error, cause);
+		else
+			new_cmdq = NULL;
+	} else {
+		new_cmdq = cmdq_get_command(cmdlist, NULL, &cdata->mouse, 0);
+		cmd_list_free(cmdlist);
 	}
 
-	cmdq1 = cmdq_new(cmdq->client);
-	cmdq1->emptyfn = cmd_if_shell_done;
-	cmdq1->data = cdata;
+	if (new_cmdq != NULL) {
+		if (cdata->cmdq == NULL)
+			cmdq_append(c, new_cmdq);
+		else
+			cmdq_insert_after(cdata->cmdq, new_cmdq);
+	}
 
-	cdata->references++;
-	cmdq_run(cmdq1, cmdlist, &cdata->mouse);
-	cmd_list_free(cmdlist);
-}
-
-static void
-cmd_if_shell_done(struct cmd_q *cmdq1)
-{
-	struct cmd_if_shell_data	*cdata = cmdq1->data;
-	struct cmd_q			*cmdq = cdata->cmdq;
-
-	if (cmdq1->client_exit >= 0)
-		cmdq->client_exit = cmdq1->client_exit;
-	cmdq_free(cmdq1);
-
-	if (--cdata->references != 0)
-		return;
-
-	if (!cmdq_free(cmdq) && !cdata->bflag)
-		cmdq_continue(cmdq);
-
-	free(cdata->cmd_else);
-	free(cdata->cmd_if);
-
-	free(cdata->file);
-	free(cdata);
+out:
+	if (cdata->cmdq != NULL)
+		cdata->cmdq->flags &= ~CMD_Q_WAITING;
 }
 
 static void
 cmd_if_shell_free(void *data)
 {
 	struct cmd_if_shell_data	*cdata = data;
-	struct cmd_q			*cmdq = cdata->cmdq;
 
-	if (--cdata->references != 0)
-		return;
-
-	if (!cmdq_free(cmdq) && !cdata->bflag)
-		cmdq_continue(cmdq);
+	server_client_unref(cdata->client);
 
 	free(cdata->cmd_else);
 	free(cdata->cmd_if);
diff --git a/cmd-load-buffer.c b/cmd-load-buffer.c
index ca886d69..ae071968 100644
--- a/cmd-load-buffer.c
+++ b/cmd-load-buffer.c
@@ -46,17 +46,24 @@ const struct cmd_entry cmd_load_buffer_entry = {
 	.exec = cmd_load_buffer_exec
 };
 
+struct cmd_load_buffer_data {
+	struct cmd_q	*cmdq;
+	char		*bufname;
+};
+
 static enum cmd_retval
 cmd_load_buffer_exec(struct cmd *self, struct cmd_q *cmdq)
 {
-	struct args	*args = self->args;
-	struct client	*c = cmdq->client;
-	struct session  *s;
-	FILE		*f;
-	const char	*path, *bufname, *cwd;
-	char		*pdata, *new_pdata, *cause, *file, resolved[PATH_MAX];
-	size_t		 psize;
-	int		 ch, error;
+	struct args			*args = self->args;
+	struct cmd_load_buffer_data	*cdata;
+	struct client			*c = cmdq->client;
+	struct session  		*s;
+	FILE				*f;
+	const char			*path, *bufname, *cwd;
+	char				*pdata, *new_pdata, *cause, *file;
+	char				 resolved[PATH_MAX];
+	size_t				 psize;
+	int				 ch, error;
 
 	bufname = NULL;
 	if (args_has(args, 'b'))
@@ -64,8 +71,12 @@ cmd_load_buffer_exec(struct cmd *self, struct cmd_q *cmdq)
 
 	path = args->argv[0];
 	if (strcmp(path, "-") == 0) {
+		cdata = xcalloc(1, sizeof *cdata);
+		cdata->cmdq = cmdq;
+		cdata->bufname = xstrdup(bufname);
+
 		error = server_set_stdin_callback(c, cmd_load_buffer_callback,
-		    (void *)bufname, &cause);
+		    cdata, &cause);
 		if (error != 0) {
 			cmdq_error(cmdq, "%s: %s", path, cause);
 			free(cause);
@@ -136,9 +147,9 @@ error:
 static void
 cmd_load_buffer_callback(struct client *c, int closed, void *data)
 {
-	const char	*bufname = data;
-	char		*pdata, *cause, *saved;
-	size_t		 psize;
+	struct cmd_load_buffer_data	*cdata = data;
+	char				*pdata, *cause, *saved;
+	size_t				 psize;
 
 	if (!closed)
 		return;
@@ -146,7 +157,7 @@ cmd_load_buffer_callback(struct client *c, int closed, void *data)
 
 	server_client_unref(c);
 	if (c->flags & CLIENT_DEAD)
-		return;
+		goto out;
 
 	psize = EVBUFFER_LENGTH(c->stdin_data);
 	if (psize == 0 || (pdata = malloc(psize + 1)) == NULL)
@@ -156,7 +167,7 @@ cmd_load_buffer_callback(struct client *c, int closed, void *data)
 	pdata[psize] = '\0';
 	evbuffer_drain(c->stdin_data, psize);
 
-	if (paste_set(pdata, psize, bufname, &cause) != 0) {
+	if (paste_set(pdata, psize, cdata->bufname, &cause) != 0) {
 		/* No context so can't use server_client_msg_error. */
 		if (~c->flags & CLIENT_UTF8) {
 			saved = cause;
@@ -168,7 +179,9 @@ cmd_load_buffer_callback(struct client *c, int closed, void *data)
 		free(pdata);
 		free(cause);
 	}
-
 out:
-	cmdq_continue(c->cmdq);
+	cdata->cmdq->flags &= ~CMD_Q_WAITING;
+
+	free(cdata->bufname);
+	free(cdata);
 }
diff --git a/cmd-new-session.c b/cmd-new-session.c
index 5924b793..e587e5ee 100644
--- a/cmd-new-session.c
+++ b/cmd-new-session.c
@@ -312,14 +312,14 @@ cmd_new_session_exec(struct cmd *self, struct cmd_q *cmdq)
 	}
 
 	if (!detached)
-		cmdq->client_exit = 0;
+		c->flags |= CLIENT_ATTACHED;
 
 	if (to_free != NULL)
 		free((void *)to_free);
 
 	cmd_find_from_session(&fs, s);
-	if (hooks_wait(s->hooks, cmdq, &fs, "after-new-session") == 0)
-		return (CMD_RETURN_WAIT);
+	hooks_insert(s->hooks, cmdq, &fs, "after-new-session");
+
 	return (CMD_RETURN_NORMAL);
 
 error:
diff --git a/cmd-new-window.c b/cmd-new-window.c
index 4d0e0057..af476913 100644
--- a/cmd-new-window.c
+++ b/cmd-new-window.c
@@ -157,8 +157,8 @@ cmd_new_window_exec(struct cmd *self, struct cmd_q *cmdq)
 		free((void *)to_free);
 
 	cmd_find_from_winlink(&fs, s, wl);
-	if (hooks_wait(s->hooks, cmdq, &fs, "after-new-window") == 0)
-		return (CMD_RETURN_WAIT);
+	hooks_insert(s->hooks, cmdq, &fs, "after-new-window");
+
 	return (CMD_RETURN_NORMAL);
 
 error:
diff --git a/cmd-queue.c b/cmd-queue.c
index 2012e871..fca3bf04 100644
--- a/cmd-queue.c
+++ b/cmd-queue.c
@@ -25,47 +25,316 @@
 
 #include "tmux.h"
 
-static enum cmd_retval	cmdq_continue_one(struct cmd_q *);
-static void		cmdq_flush(struct cmd_q *);
+/* Global command queue. */
+static struct cmd_q_list global_queue = TAILQ_HEAD_INITIALIZER(global_queue);
 
-/* Create new command queue. */
+/* Get command queue name. */
+static const char *
+cmdq_name(struct client *c)
+{
+	static char	s[32];
+
+	if (c == NULL)
+		return ("<global>");
+	xsnprintf(s, sizeof s, "<%p>", c);
+	return (s);
+}
+
+/* Get command queue from client. */
+static struct cmd_q_list *
+cmdq_get(struct client *c)
+{
+	if (c == NULL)
+		return (&global_queue);
+	return (&c->queue);
+}
+
+/* Append an item. */
+void
+cmdq_append(struct client *c, struct cmd_q *cmdq)
+{
+	struct cmd_q_list	*queue = cmdq_get(c);
+	struct cmd_q		*next;
+
+	do {
+		next = cmdq->next;
+		cmdq->next = NULL;
+
+		if (c != NULL)
+			c->references++;
+		cmdq->client = c;
+
+		cmdq->queue = queue;
+		TAILQ_INSERT_TAIL(queue, cmdq, entry);
+
+		cmdq = next;
+	} while (cmdq != NULL);
+}
+
+/* Insert an item. */
+void
+cmdq_insert_after(struct cmd_q *after, struct cmd_q *cmdq)
+{
+	struct client		*c = after->client;
+	struct cmd_q_list	*queue = after->queue;
+	struct cmd_q		*next;
+
+	do {
+		next = cmdq->next;
+		cmdq->next = NULL;
+
+		if (c != NULL)
+			c->references++;
+		cmdq->client = c;
+
+		cmdq->queue = queue;
+		if (after->next != NULL)
+			TAILQ_INSERT_AFTER(queue, after->next, cmdq, entry);
+		else
+			TAILQ_INSERT_AFTER(queue, after, cmdq, entry);
+		after->next = cmdq;
+
+		cmdq = next;
+	} while (cmdq != NULL);
+}
+
+/* Remove an item. */
+static void
+cmdq_remove(struct cmd_q *cmdq)
+{
+	free((void *)cmdq->hook);
+
+	if (cmdq->client != NULL)
+		server_client_unref(cmdq->client);
+
+	if (cmdq->type == CMD_Q_COMMAND)
+		cmd_list_free(cmdq->cmdlist);
+
+	TAILQ_REMOVE(cmdq->queue, cmdq, entry);
+	free(cmdq);
+}
+
+/* Set command group. */
+static u_int
+cmdq_next_group(void)
+{
+	static u_int	group;
+
+	return (++group);
+}
+
+/* Remove all subsequent items that match this item's group. */
+static void
+cmdq_remove_group(struct cmd_q *cmdq)
+{
+	struct cmd_q	*this, *next;
+
+	this = TAILQ_NEXT(cmdq, entry);
+	while (this != NULL) {
+		next = TAILQ_NEXT(this, entry);
+		if (this->group == cmdq->group)
+			cmdq_remove(this);
+		this = next;
+	}
+}
+
+/* Get a command for the command queue. */
 struct cmd_q *
-cmdq_new(struct client *c)
+cmdq_get_command(struct cmd_list *cmdlist, struct cmd_find_state *current,
+    struct mouse_event *m, int flags)
+{
+	struct cmd_q	*cmdq, *first = NULL, *last = NULL;
+	struct cmd	*cmd;
+	u_int		 group = cmdq_next_group();
+
+	TAILQ_FOREACH(cmd, &cmdlist->list, qentry) {
+		cmdq = xcalloc(1, sizeof *cmdq);
+		cmdq->type = CMD_Q_COMMAND;
+		cmdq->group = group;
+		cmdq->flags = flags;
+
+		cmdq->cmdlist = cmdlist;
+		cmdq->cmd = cmd;
+
+		if (current != NULL)
+			cmd_find_copy_state(&cmdq->current, current);
+		if (m != NULL)
+			memcpy(&cmdq->mouse, m, sizeof cmdq->mouse);
+		cmdlist->references++;
+
+		if (first == NULL)
+			first = cmdq;
+		if (last != NULL)
+			last->next = cmdq;
+		last = cmdq;
+	}
+	return (first);
+}
+
+/* Fire command on command queue. */
+static enum cmd_retval
+cmdq_fire_command(struct cmd_q *cmdq)
+{
+	struct client		*c = cmdq->client;
+	struct cmd		*cmd = cmdq->cmd;
+	enum cmd_retval		 retval;
+	const char		*name;
+	struct cmd_find_state	*fsp, fs;
+	int			 flags;
+
+	flags = !!(cmd->flags & CMD_CONTROL);
+	cmdq_guard(cmdq, "begin", flags);
+
+	if (cmd_prepare_state(cmd, cmdq) != 0) {
+		retval = CMD_RETURN_ERROR;
+		goto out;
+	}
+	if (cmdq->client == NULL)
+		cmdq->client = cmd_find_client(cmdq, NULL, CMD_FIND_QUIET);
+
+	retval = cmd->entry->exec(cmd, cmdq);
+	if (retval == CMD_RETURN_ERROR)
+		goto out;
+
+	if (cmd->entry->flags & CMD_AFTERHOOK) {
+		name = cmd->entry->name;
+		if (cmd_find_valid_state(&cmdq->state.tflag))
+			fsp = &cmdq->state.tflag;
+		else {
+			if (cmd_find_current(&fs, cmdq, CMD_FIND_QUIET) != 0)
+				goto out;
+			fsp = &fs;
+		}
+		hooks_insert(fsp->s->hooks, cmdq, fsp, "after-%s", name);
+	}
+
+out:
+	cmdq->client = c;
+	if (retval == CMD_RETURN_ERROR)
+		cmdq_guard(cmdq, "error", flags);
+	else
+		cmdq_guard(cmdq, "end", flags);
+	return (retval);
+}
+
+/* Get a callback for the command queue. */
+struct cmd_q *
+cmdq_get_callback(cmd_q_cb cb, void *data)
 {
 	struct cmd_q	*cmdq;
 
 	cmdq = xcalloc(1, sizeof *cmdq);
-	cmdq->references = 1;
+	cmdq->type = CMD_Q_CALLBACK;
+	cmdq->group = 0;
 	cmdq->flags = 0;
 
-	cmdq->client = c;
-	cmdq->client_exit = -1;
-
-	TAILQ_INIT(&cmdq->queue);
-	cmdq->item = NULL;
-	cmdq->cmd = NULL;
-
-	cmd_find_clear_state(&cmdq->current, NULL, 0);
-	cmdq->parent = NULL;
+	cmdq->cb = cb;
+	cmdq->data = data;
 
 	return (cmdq);
 }
 
-/* Free command queue */
-int
-cmdq_free(struct cmd_q *cmdq)
+/* Fire callback on callback queue. */
+static enum cmd_retval
+cmdq_fire_callback(struct cmd_q *cmdq)
 {
-	log_debug("cmdq %p free: %u references", cmdq, cmdq->references);
+	return (cmdq->cb(cmdq, cmdq->data));
+}
 
-	if (--cmdq->references != 0) {
-		if (cmdq->flags & CMD_Q_DEAD)
-			return (1);
+/* Process next item on command queue. */
+u_int
+cmdq_next(struct client *c)
+{
+	struct cmd_q_list	*queue = cmdq_get(c);
+	const char		*name = cmdq_name(c);
+	struct cmd_q		*cmdq;
+	enum cmd_retval		 retval;
+	u_int			 items = 0;
+	static u_int		 number;
+
+	if (TAILQ_EMPTY(queue)) {
+		log_debug("%s %s: empty", __func__, name);
+		return (0);
+	}
+	if (TAILQ_FIRST(queue)->flags & CMD_Q_WAITING) {
+		log_debug("%s %s: waiting", __func__, name);
 		return (0);
 	}
 
-	cmdq_flush(cmdq);
-	free(cmdq);
-	return (1);
+	log_debug("%s %s: enter", __func__, name);
+	for (;;) {
+		cmdq = TAILQ_FIRST(queue);
+		if (cmdq == NULL)
+			break;
+		log_debug("%s %s: type %d, flags %x", __func__, name,
+		    cmdq->type, cmdq->flags);
+
+		/*
+		 * Any item with the waiting flag set waits until an external
+		 * event clears the flag (for example, a job - look at
+		 * run-shell).
+		 */
+		if (cmdq->flags & CMD_Q_WAITING)
+			goto waiting;
+
+		/*
+		 * Items are only fired once, once the fired flag is set, a
+		 * waiting flag can only be cleared by an external event.
+		 */
+		if (~cmdq->flags & CMD_Q_FIRED) {
+			cmdq->time = time(NULL);
+			cmdq->number = ++number;
+
+			switch (cmdq->type)
+			{
+			case CMD_Q_COMMAND:
+				retval = cmdq_fire_command(cmdq);
+
+				/*
+				 * If a command returns an error, remove any
+				 * subsequent commands in the same group.
+				 */
+				if (retval == CMD_RETURN_ERROR)
+					cmdq_remove_group(cmdq);
+				break;
+			case CMD_Q_CALLBACK:
+				retval = cmdq_fire_callback(cmdq);
+				break;
+			default:
+				retval = CMD_RETURN_ERROR;
+				break;
+			}
+			cmdq->flags |= CMD_Q_FIRED;
+
+			if (retval == CMD_RETURN_WAIT) {
+				cmdq->flags |= CMD_Q_WAITING;
+				goto waiting;
+			}
+			items++;
+		}
+		cmdq_remove(cmdq);
+	}
+
+	log_debug("%s %s: exit (empty)", __func__, name);
+	return (items);
+
+waiting:
+	log_debug("%s %s: exit (wait)", __func__, name);
+	return (items);
+}
+
+/* Print a guard line. */
+void
+cmdq_guard(struct cmd_q *cmdq, const char *guard, int flags)
+{
+	struct client	*c = cmdq->client;
+
+	if (c == NULL || !(c->flags & CLIENT_CONTROL))
+		return;
+
+	evbuffer_add_printf(c->stdout_data, "%%%s %ld %u %d\n", guard,
+	    (long)cmdq->time, cmdq->number, flags);
+	server_client_push_stdout(c);
 }
 
 /* Show message from command. */
@@ -140,175 +409,3 @@ cmdq_error(struct cmd_q *cmdq, const char *fmt, ...)
 
 	free(msg);
 }
-
-/* Print a guard line. */
-void
-cmdq_guard(struct cmd_q *cmdq, const char *guard, int flags)
-{
-	struct client	*c = cmdq->client;
-
-	if (c == NULL || !(c->flags & CLIENT_CONTROL))
-		return;
-
-	evbuffer_add_printf(c->stdout_data, "%%%s %ld %u %d\n", guard,
-	    (long) cmdq->time, cmdq->number, flags);
-	server_client_push_stdout(c);
-}
-
-/* Add command list to queue and begin processing if needed. */
-void
-cmdq_run(struct cmd_q *cmdq, struct cmd_list *cmdlist, struct mouse_event *m)
-{
-	cmdq_append(cmdq, cmdlist, m);
-
-	if (cmdq->item == NULL) {
-		cmdq->cmd = NULL;
-		cmdq_continue(cmdq);
-	}
-}
-
-/* Add command list to queue. */
-void
-cmdq_append(struct cmd_q *cmdq, struct cmd_list *cmdlist, struct mouse_event *m)
-{
-	struct cmd_q_item	*item;
-
-	item = xcalloc(1, sizeof *item);
-	item->cmdlist = cmdlist;
-	TAILQ_INSERT_TAIL(&cmdq->queue, item, qentry);
-	cmdlist->references++;
-
-	if (m != NULL)
-		memcpy(&item->mouse, m, sizeof item->mouse);
-	else
-		item->mouse.valid = 0;
-}
-
-/* Process one command. */
-static enum cmd_retval
-cmdq_continue_one(struct cmd_q *cmdq)
-{
-	struct cmd_list		*cmdlist = cmdq->item->cmdlist;
-	struct cmd		*cmd = cmdq->cmd;
-	enum cmd_retval		 retval;
-	char			*tmp;
-	int			 flags = !!(cmd->flags & CMD_CONTROL);
-	const char		*name;
-	struct cmd_find_state	*fsp, fs;
-
-	cmdlist->references++;
-
-	tmp = cmd_print(cmd);
-	log_debug("cmdq %p: %s", cmdq, tmp);
-	free(tmp);
-
-	cmdq->time = time(NULL);
-	cmdq->number++;
-
-	cmdq_guard(cmdq, "begin", flags);
-
-	if (cmd_prepare_state(cmd, cmdq, cmdq->parent) != 0)
-		goto error;
-
-	retval = cmd->entry->exec(cmd, cmdq);
-	if (retval == CMD_RETURN_ERROR)
-		goto error;
-
-	if (~cmd->entry->flags & CMD_AFTERHOOK)
-		goto end;
-
-	if (cmd_find_valid_state(&cmdq->state.tflag))
-		fsp = &cmdq->state.tflag;
-	else {
-		if (cmd_find_current(&fs, cmdq, CMD_FIND_QUIET) != 0)
-			goto end;
-		fsp = &fs;
-	}
-	name = cmd->entry->name;
-	if (hooks_wait(fsp->s->hooks, cmdq, fsp, "after-%s", name) == 0)
-		retval = CMD_RETURN_WAIT;
-
-end:
-	cmdq_guard(cmdq, "end", flags);
-	cmd_list_free(cmdlist);
-	return (retval);
-
-error:
-	cmdq_guard(cmdq, "error", flags);
-	cmd_list_free(cmdlist);
-	return (CMD_RETURN_ERROR);
-}
-
-/* Continue processing command queue. Returns 1 if finishes empty. */
-int
-cmdq_continue(struct cmd_q *cmdq)
-{
-	struct client		*c = cmdq->client;
-	struct cmd_q_item	*next;
-	enum cmd_retval		 retval;
-	int			 empty;
-
-	log_debug("continuing cmdq %p: flags %#x (%p)", cmdq, cmdq->flags, c);
-	cmdq->references++;
-
-	empty = TAILQ_EMPTY(&cmdq->queue);
-	if (empty)
-		goto empty;
-
-	if (cmdq->item == NULL) {
-		cmdq->item = TAILQ_FIRST(&cmdq->queue);
-		cmdq->cmd = TAILQ_FIRST(&cmdq->item->cmdlist->list);
-	} else
-		cmdq->cmd = TAILQ_NEXT(cmdq->cmd, qentry);
-
-	do {
-		while (cmdq->cmd != NULL) {
-			retval = cmdq_continue_one(cmdq);
-			if (retval == CMD_RETURN_ERROR)
-				break;
-			if (retval == CMD_RETURN_WAIT)
-				goto out;
-			if (retval == CMD_RETURN_STOP) {
-				cmdq_flush(cmdq);
-				goto empty;
-			}
-			cmdq->cmd = TAILQ_NEXT(cmdq->cmd, qentry);
-		}
-		next = TAILQ_NEXT(cmdq->item, qentry);
-
-		TAILQ_REMOVE(&cmdq->queue, cmdq->item, qentry);
-		cmd_list_free(cmdq->item->cmdlist);
-		free(cmdq->item);
-
-		cmdq->item = next;
-		if (cmdq->item != NULL)
-			cmdq->cmd = TAILQ_FIRST(&cmdq->item->cmdlist->list);
-	} while (cmdq->item != NULL);
-
-empty:
-	log_debug("cmdq %p empty", cmdq);
-	if (cmdq->client_exit > 0)
-		cmdq->client->flags |= CLIENT_EXIT;
-	if (cmdq->emptyfn != NULL)
-		cmdq->emptyfn(cmdq);
-	empty = 1;
-
-out:
-	cmdq_free(cmdq);
-	return (empty);
-}
-
-/* Flush command queue. */
-static void
-cmdq_flush(struct cmd_q *cmdq)
-{
-	struct cmd_q_item	*item, *item1;
-
-	TAILQ_FOREACH_SAFE(item, &cmdq->queue, qentry, item1) {
-		TAILQ_REMOVE(&cmdq->queue, item, qentry);
-		cmd_list_free(item->cmdlist);
-		free(item);
-	}
-	cmdq->item = NULL;
-}
-
diff --git a/cmd-resize-pane.c b/cmd-resize-pane.c
index be652b56..3360bd6b 100644
--- a/cmd-resize-pane.c
+++ b/cmd-resize-pane.c
@@ -60,12 +60,12 @@ cmd_resize_pane_exec(struct cmd *self, struct cmd_q *cmdq)
 	int			 x, y;
 
 	if (args_has(args, 'M')) {
-		if (cmd_mouse_window(&cmdq->item->mouse, &s) == NULL)
+		if (cmd_mouse_window(&cmdq->mouse, &s) == NULL)
 			return (CMD_RETURN_NORMAL);
 		if (c == NULL || c->session != s)
 			return (CMD_RETURN_NORMAL);
 		c->tty.mouse_drag_update = cmd_resize_pane_mouse_update;
-		cmd_resize_pane_mouse_update(c, &cmdq->item->mouse);
+		cmd_resize_pane_mouse_update(c, &cmdq->mouse);
 		return (CMD_RETURN_NORMAL);
 	}
 
diff --git a/cmd-run-shell.c b/cmd-run-shell.c
index c81c76f8..ad2d4c2f 100644
--- a/cmd-run-shell.c
+++ b/cmd-run-shell.c
@@ -51,7 +51,6 @@ const struct cmd_entry cmd_run_shell_entry = {
 struct cmd_run_shell_data {
 	char		*cmd;
 	struct cmd_q	*cmdq;
-	int		 bflag;
 	int		 wp_id;
 };
 
@@ -92,6 +91,7 @@ cmd_run_shell_exec(struct cmd *self, struct cmd_q *cmdq)
 		cwd = s->cwd;
 	else
 		cwd = NULL;
+
 	ft = format_create(cmdq, 0);
 	format_defaults(ft, cmdq->state.c, s, wl, wp);
 	shellcmd = format_expand(ft, args->argv[0]);
@@ -99,20 +99,24 @@ cmd_run_shell_exec(struct cmd *self, struct cmd_q *cmdq)
 
 	cdata = xcalloc(1, sizeof *cdata);
 	cdata->cmd = shellcmd;
-	cdata->bflag = args_has(args, 'b');
 
 	if (args_has(args, 't') && wp != NULL)
 		cdata->wp_id = wp->id;
 	else
 		cdata->wp_id = -1;
 
-	cdata->cmdq = cmdq;
-	cmdq->references++;
+	if (args_has(args, 't') && wp != NULL)
+		cdata->wp_id = wp->id;
+	else
+		cdata->wp_id = -1;
+
+	if (!args_has(args, 'b'))
+		cdata->cmdq = cmdq;
 
 	job_run(shellcmd, s, cwd, cmd_run_shell_callback, cmd_run_shell_free,
 	    cdata);
 
-	if (cdata->bflag)
+	if (args_has(args, 'b'))
 		return (CMD_RETURN_NORMAL);
 	return (CMD_RETURN_WAIT);
 }
@@ -121,16 +125,11 @@ static void
 cmd_run_shell_callback(struct job *job)
 {
 	struct cmd_run_shell_data	*cdata = job->data;
-	struct cmd_q			*cmdq = cdata->cmdq;
-	char				*cmd, *msg, *line;
+	char				*cmd = cdata->cmd, *msg, *line;
 	size_t				 size;
 	int				 retcode;
 	u_int				 lines;
 
-	if (cmdq->flags & CMD_Q_DEAD)
-		return;
-	cmd = cdata->cmd;
-
 	lines = 0;
 	do {
 		if ((line = evbuffer_readline(job->event->input)) != NULL) {
@@ -163,16 +162,15 @@ cmd_run_shell_callback(struct job *job)
 	if (msg != NULL)
 		cmd_run_shell_print(job, msg);
 	free(msg);
+
+	if (cdata->cmdq != NULL)
+		cdata->cmdq->flags &= ~CMD_Q_WAITING;
 }
 
 static void
 cmd_run_shell_free(void *data)
 {
 	struct cmd_run_shell_data	*cdata = data;
-	struct cmd_q			*cmdq = cdata->cmdq;
-
-	if (!cmdq_free(cmdq) && !cdata->bflag)
-		cmdq_continue(cmdq);
 
 	free(cdata->cmd);
 	free(cdata);
diff --git a/cmd-send-keys.c b/cmd-send-keys.c
index 062a13d7..94316834 100644
--- a/cmd-send-keys.c
+++ b/cmd-send-keys.c
@@ -62,7 +62,7 @@ cmd_send_keys_exec(struct cmd *self, struct cmd_q *cmdq)
 	struct client		*c = cmdq->state.c;
 	struct window_pane	*wp = cmdq->state.tflag.wp;
 	struct session		*s = cmdq->state.tflag.s;
-	struct mouse_event	*m = &cmdq->item->mouse;
+	struct mouse_event	*m = &cmdq->mouse;
 	const u_char		*keystr;
 	int			 i, literal;
 	key_code		 key;
diff --git a/cmd-source-file.c b/cmd-source-file.c
index 336a79e6..6f9c4451 100644
--- a/cmd-source-file.c
+++ b/cmd-source-file.c
@@ -28,7 +28,7 @@
 
 static enum cmd_retval	cmd_source_file_exec(struct cmd *, struct cmd_q *);
 
-static void		cmd_source_file_done(struct cmd_q *);
+static enum cmd_retval	cmd_source_file_done(struct cmd_q *, void *);
 
 const struct cmd_entry cmd_source_file_entry = {
 	.name = "source-file",
@@ -45,53 +45,31 @@ static enum cmd_retval
 cmd_source_file_exec(struct cmd *self, struct cmd_q *cmdq)
 {
 	struct args	*args = self->args;
-	struct cmd_q	*cmdq1;
+	struct client	*c = cmdq->client;
 	int		 quiet;
-
-	cmdq1 = cmdq_new(cmdq->client);
-	cmdq1->emptyfn = cmd_source_file_done;
-	cmdq1->data = cmdq;
+	struct cmd_q	*new_cmdq;
 
 	quiet = args_has(args, 'q');
-	switch (load_cfg(args->argv[0], cmdq1, quiet)) {
+	switch (load_cfg(args->argv[0], c, cmdq, quiet)) {
 	case -1:
-		cmdq_free(cmdq1);
-		if (cfg_references == 0) {
+		if (cfg_finished)
 			cfg_print_causes(cmdq);
-			return (CMD_RETURN_ERROR);
-		}
-		return (CMD_RETURN_NORMAL);
+		return (CMD_RETURN_ERROR);
 	case 0:
-		cmdq_free(cmdq1);
-		if (cfg_references == 0)
+		if (cfg_finished)
 			cfg_print_causes(cmdq);
 		return (CMD_RETURN_NORMAL);
 	}
-
-	log_debug("%s: cmdq %p, parent %p", __func__, cmdq1, cmdq);
-
-	cmdq->references++;
-	cfg_references++;
-
-	cmdq_continue(cmdq1);
-	return (CMD_RETURN_WAIT);
+	if (cfg_finished) {
+		new_cmdq = cmdq_get_callback(cmd_source_file_done, NULL);
+		cmdq_insert_after(cmdq, new_cmdq);
+	}
+	return (CMD_RETURN_NORMAL);
 }
 
-static void
-cmd_source_file_done(struct cmd_q *cmdq1)
+static enum cmd_retval
+cmd_source_file_done(struct cmd_q *cmdq, __unused void *data)
 {
-	struct cmd_q	*cmdq = cmdq1->data;
-
-	log_debug("%s: cmdq %p, parent %p", __func__, cmdq1, cmdq);
-
-	if (cmdq1->client_exit >= 0)
-		cmdq->client_exit = cmdq1->client_exit;
-	cmdq_free(cmdq1);
-
-	cfg_references--;
-	if (cmdq_free(cmdq))
-		return;
-	if (cfg_references == 0)
-		cfg_print_causes(cmdq);
-	cmdq_continue(cmdq);
+	cfg_print_causes(cmdq);
+	return (CMD_RETURN_NORMAL);
 }
diff --git a/cmd-split-window.c b/cmd-split-window.c
index 8fddee4b..013391a6 100644
--- a/cmd-split-window.c
+++ b/cmd-split-window.c
@@ -187,8 +187,8 @@ cmd_split_window_exec(struct cmd *self, struct cmd_q *cmdq)
 	fs.w = w;
 	fs.wp = new_wp;
 	cmd_find_log_state(__func__, &fs);
-	if (hooks_wait(s->hooks, cmdq, &fs, "after-split-window") == 0)
-		return (CMD_RETURN_WAIT);
+	hooks_insert(s->hooks, cmdq, &fs, "after-split-window");
+
 	return (CMD_RETURN_NORMAL);
 
 error:
diff --git a/cmd-wait-for.c b/cmd-wait-for.c
index fb2fb699..505e4d63 100644
--- a/cmd-wait-for.c
+++ b/cmd-wait-for.c
@@ -41,13 +41,18 @@ const struct cmd_entry cmd_wait_for_entry = {
 	.exec = cmd_wait_for_exec
 };
 
+struct wait_item {
+	struct cmd_q		*cmdq;
+	TAILQ_ENTRY(wait_item)	 entry;
+};
+
 struct wait_channel {
 	const char	       *name;
 	int			locked;
 	int			woken;
 
-	TAILQ_HEAD(, cmd_q)	waiters;
-	TAILQ_HEAD(, cmd_q)	lockers;
+	TAILQ_HEAD(, wait_item)	waiters;
+	TAILQ_HEAD(, wait_item)	lockers;
 
 	RB_ENTRY(wait_channel)	entry;
 };
@@ -135,7 +140,7 @@ static enum cmd_retval
 cmd_wait_for_signal(__unused struct cmd_q *cmdq, const char *name,
     struct wait_channel *wc)
 {
-	struct cmd_q	*wq, *wq1;
+	struct wait_item	*wi, *wi1;
 
 	if (wc == NULL)
 		wc = cmd_wait_for_add(name);
@@ -147,10 +152,11 @@ cmd_wait_for_signal(__unused struct cmd_q *cmdq, const char *name,
 	}
 	log_debug("signal wait channel %s, with waiters", wc->name);
 
-	TAILQ_FOREACH_SAFE(wq, &wc->waiters, waitentry, wq1) {
-		TAILQ_REMOVE(&wc->waiters, wq, waitentry);
-		if (!cmdq_free(wq))
-			cmdq_continue(wq);
+	TAILQ_FOREACH_SAFE(wi, &wc->waiters, entry, wi1) {
+		wi->cmdq->flags &= ~CMD_Q_WAITING;
+
+		TAILQ_REMOVE(&wc->waiters, wi, entry);
+		free(wi);
 	}
 
 	cmd_wait_for_remove(wc);
@@ -158,10 +164,10 @@ cmd_wait_for_signal(__unused struct cmd_q *cmdq, const char *name,
 }
 
 static enum cmd_retval
-cmd_wait_for_wait(struct cmd_q *cmdq, const char *name,
-    struct wait_channel *wc)
+cmd_wait_for_wait(struct cmd_q *cmdq, const char *name, struct wait_channel *wc)
 {
-	struct client	*c = cmdq->client;
+	struct client		*c = cmdq->client;
+	struct wait_item	*wi;
 
 	if (c == NULL || c->session != NULL) {
 		cmdq_error(cmdq, "not able to wait");
@@ -178,16 +184,18 @@ cmd_wait_for_wait(struct cmd_q *cmdq, const char *name,
 	}
 	log_debug("wait channel %s not woken (%p)", wc->name, c);
 
-	TAILQ_INSERT_TAIL(&wc->waiters, cmdq, waitentry);
-	cmdq->references++;
+	wi = xcalloc(1, sizeof *wi);
+	wi->cmdq = cmdq;
+	TAILQ_INSERT_TAIL(&wc->waiters, wi, entry);
 
 	return (CMD_RETURN_WAIT);
 }
 
 static enum cmd_retval
-cmd_wait_for_lock(struct cmd_q *cmdq, const char *name,
-    struct wait_channel *wc)
+cmd_wait_for_lock(struct cmd_q *cmdq, const char *name, struct wait_channel *wc)
 {
+	struct wait_item	*wi;
+
 	if (cmdq->client == NULL || cmdq->client->session != NULL) {
 		cmdq_error(cmdq, "not able to lock");
 		return (CMD_RETURN_ERROR);
@@ -197,8 +205,9 @@ cmd_wait_for_lock(struct cmd_q *cmdq, const char *name,
 		wc = cmd_wait_for_add(name);
 
 	if (wc->locked) {
-		TAILQ_INSERT_TAIL(&wc->lockers, cmdq, waitentry);
-		cmdq->references++;
+		wi = xcalloc(1, sizeof *wi);
+		wi->cmdq = cmdq;
+		TAILQ_INSERT_TAIL(&wc->lockers, wi, entry);
 		return (CMD_RETURN_WAIT);
 	}
 	wc->locked = 1;
@@ -210,17 +219,17 @@ static enum cmd_retval
 cmd_wait_for_unlock(struct cmd_q *cmdq, const char *name,
     struct wait_channel *wc)
 {
-	struct cmd_q	*wq;
+	struct wait_item	*wi;
 
 	if (wc == NULL || !wc->locked) {
 		cmdq_error(cmdq, "channel %s not locked", name);
 		return (CMD_RETURN_ERROR);
 	}
 
-	if ((wq = TAILQ_FIRST(&wc->lockers)) != NULL) {
-		TAILQ_REMOVE(&wc->lockers, wq, waitentry);
-		if (!cmdq_free(wq))
-			cmdq_continue(wq);
+	if ((wi = TAILQ_FIRST(&wc->lockers)) != NULL) {
+		wi->cmdq->flags &= ~CMD_Q_WAITING;
+		TAILQ_REMOVE(&wc->lockers, wi, entry);
+		free(wi);
 	} else {
 		wc->locked = 0;
 		cmd_wait_for_remove(wc);
@@ -233,19 +242,19 @@ void
 cmd_wait_for_flush(void)
 {
 	struct wait_channel	*wc, *wc1;
-	struct cmd_q		*wq, *wq1;
+	struct wait_item	*wi, *wi1;
 
 	RB_FOREACH_SAFE(wc, wait_channels, &wait_channels, wc1) {
-		TAILQ_FOREACH_SAFE(wq, &wc->waiters, waitentry, wq1) {
-			TAILQ_REMOVE(&wc->waiters, wq, waitentry);
-			if (!cmdq_free(wq))
-				cmdq_continue(wq);
+		TAILQ_FOREACH_SAFE(wi, &wc->waiters, entry, wi1) {
+			wi->cmdq->flags &= ~CMD_Q_WAITING;
+			TAILQ_REMOVE(&wc->waiters, wi, entry);
+			free(wi);
 		}
 		wc->woken = 1;
-		TAILQ_FOREACH_SAFE(wq, &wc->lockers, waitentry, wq1) {
-			TAILQ_REMOVE(&wc->lockers, wq, waitentry);
-			if (!cmdq_free(wq))
-				cmdq_continue(wq);
+		TAILQ_FOREACH_SAFE(wi, &wc->lockers, entry, wi1) {
+			wi->cmdq->flags &= ~CMD_Q_WAITING;
+			TAILQ_REMOVE(&wc->lockers, wi, entry);
+			free(wi);
 		}
 		wc->locked = 0;
 		cmd_wait_for_remove(wc);
diff --git a/cmd.c b/cmd.c
index 14ef7f21..a7de1f58 100644
--- a/cmd.c
+++ b/cmd.c
@@ -389,12 +389,11 @@ usage:
 
 static int
 cmd_prepare_state_flag(char c, const char *target, enum cmd_entry_flag flag,
-    struct cmd_q *cmdq, struct cmd_q *parent)
+    struct cmd_q *cmdq)
 {
 	int			 targetflags, error;
 	struct cmd_find_state	*fs = NULL;
-	struct cmd_find_state	*current = NULL;
-	struct cmd_find_state	 tmp;
+	struct cmd_find_state	 current;
 
 	if (flag == CMD_NONE ||
 	    flag == CMD_CLIENT ||
@@ -448,21 +447,12 @@ cmd_prepare_state_flag(char c, const char *target, enum cmd_entry_flag flag,
 	default:
 		fatalx("unknown %cflag %d", c, flag);
 	}
-
 	log_debug("%s: flag %c %d %#x", __func__, c, flag, targetflags);
-	if (parent != NULL) {
-		if (c == 't')
-			current = &parent->state.tflag;
-		else if (c == 's')
-			current = &parent->state.sflag;
-	}
-	if (current == NULL || !cmd_find_valid_state(current)) {
-		error = cmd_find_current(&tmp, cmdq, targetflags);
-		if (error != 0 && ~targetflags & CMD_FIND_QUIET)
-			return (-1);
-		current = &tmp;
-	}
-	if (!cmd_find_empty_state(current) && !cmd_find_valid_state(current))
+
+	error = cmd_find_current(&current, cmdq, targetflags);
+	if (error != 0 && ~targetflags & CMD_FIND_QUIET)
+		return (-1);
+	if (!cmd_find_empty_state(&current) && !cmd_find_valid_state(&current))
 		fatalx("invalid current state");
 
 	switch (flag) {
@@ -474,13 +464,13 @@ cmd_prepare_state_flag(char c, const char *target, enum cmd_entry_flag flag,
 	case CMD_SESSION_CANFAIL:
 	case CMD_SESSION_PREFERUNATTACHED:
 	case CMD_SESSION_WITHPANE:
-		error = cmd_find_target(fs, current, cmdq, target,
+		error = cmd_find_target(fs, &current, cmdq, target,
 		    CMD_FIND_SESSION, targetflags);
 		if (error != 0 && ~targetflags & CMD_FIND_QUIET)
 			return (-1);
 		break;
 	case CMD_MOVEW_R:
-		error = cmd_find_target(fs, current, cmdq, target,
+		error = cmd_find_target(fs, &current, cmdq, target,
 		    CMD_FIND_SESSION, CMD_FIND_QUIET);
 		if (error == 0)
 			break;
@@ -489,7 +479,7 @@ cmd_prepare_state_flag(char c, const char *target, enum cmd_entry_flag flag,
 	case CMD_WINDOW_CANFAIL:
 	case CMD_WINDOW_MARKED:
 	case CMD_WINDOW_INDEX:
-		error = cmd_find_target(fs, current, cmdq, target,
+		error = cmd_find_target(fs, &current, cmdq, target,
 		    CMD_FIND_WINDOW, targetflags);
 		if (error != 0 && ~targetflags & CMD_FIND_QUIET)
 			return (-1);
@@ -497,7 +487,7 @@ cmd_prepare_state_flag(char c, const char *target, enum cmd_entry_flag flag,
 	case CMD_PANE:
 	case CMD_PANE_CANFAIL:
 	case CMD_PANE_MARKED:
-		error = cmd_find_target(fs, current, cmdq, target,
+		error = cmd_find_target(fs, &current, cmdq, target,
 		    CMD_FIND_PANE, targetflags);
 		if (error != 0 && ~targetflags & CMD_FIND_QUIET)
 			return (-1);
@@ -509,14 +499,14 @@ cmd_prepare_state_flag(char c, const char *target, enum cmd_entry_flag flag,
 }
 
 int
-cmd_prepare_state(struct cmd *cmd, struct cmd_q *cmdq, struct cmd_q *parent)
+cmd_prepare_state(struct cmd *cmd, struct cmd_q *cmdq)
 {
-	const struct cmd_entry		*entry = cmd->entry;
-	struct cmd_state		*state = &cmdq->state;
-	char				*tmp;
-	enum cmd_entry_flag		 flag;
-	const char			*s;
-	int				 error;
+	const struct cmd_entry	*entry = cmd->entry;
+	struct cmd_state	*state = &cmdq->state;
+	char			*tmp;
+	enum cmd_entry_flag	 flag;
+	const char		*s;
+	int			 error;
 
 	tmp = cmd_print(cmd);
 	log_debug("preparing state for %s (client %p)", tmp, cmdq->client);
@@ -545,18 +535,19 @@ cmd_prepare_state(struct cmd *cmd, struct cmd_q *cmdq, struct cmd_q *parent)
 		state->c = cmd_find_client(cmdq, s, 1);
 		break;
 	}
+	log_debug("using client %p", state->c);
 
 	s = args_get(cmd->args, 't');
 	log_debug("preparing -t state: target %s", s == NULL ? "none" : s);
 
-	error = cmd_prepare_state_flag('t', s, entry->tflag, cmdq, parent);
+	error = cmd_prepare_state_flag('t', s, entry->tflag, cmdq);
 	if (error != 0)
 		return (error);
 
 	s = args_get(cmd->args, 's');
 	log_debug("preparing -s state: target %s", s == NULL ? "none" : s);
 
-	error = cmd_prepare_state_flag('s', s, entry->sflag, cmdq, parent);
+	error = cmd_prepare_state_flag('s', s, entry->sflag, cmdq);
 	if (error != 0)
 		return (error);
 
diff --git a/control.c b/control.c
index f6dedca8..cd605f4f 100644
--- a/control.c
+++ b/control.c
@@ -49,6 +49,21 @@ control_write_buffer(struct client *c, struct evbuffer *buffer)
 	server_client_push_stdout(c);
 }
 
+/* Control error callback. */
+static enum cmd_retval
+control_error(struct cmd_q *cmdq, void *data)
+{
+	struct client	*c = cmdq->client;
+	char		*error = data;
+
+	cmdq_guard(cmdq, "begin", 1);
+	control_write(c, "parse error: %s", error);
+	cmdq_guard(cmdq, "error", 1);
+
+	free(error);
+	return (CMD_RETURN_NORMAL);
+}
+
 /* Control input callback. Read lines and fire commands. */
 void
 control_callback(struct client *c, int closed, __unused void *data)
@@ -56,6 +71,7 @@ control_callback(struct client *c, int closed, __unused void *data)
 	char		*line, *cause;
 	struct cmd_list	*cmdlist;
 	struct cmd	*cmd;
+	struct cmd_q	*cmdq;
 
 	if (closed)
 		c->flags |= CLIENT_EXIT;
@@ -70,18 +86,13 @@ control_callback(struct client *c, int closed, __unused void *data)
 		}
 
 		if (cmd_string_parse(line, &cmdlist, NULL, 0, &cause) != 0) {
-			c->cmdq->time = time(NULL);
-			c->cmdq->number++;
-
-			cmdq_guard(c->cmdq, "begin", 1);
-			control_write(c, "parse error: %s", cause);
-			cmdq_guard(c->cmdq, "error", 1);
-
-			free(cause);
+			cmdq = cmdq_get_callback(control_error, cause);
+			cmdq_append(c, cmdq);
 		} else {
 			TAILQ_FOREACH(cmd, &cmdlist->list, qentry)
 				cmd->flags |= CMD_CONTROL;
-			cmdq_run(c->cmdq, cmdlist, NULL);
+			cmdq = cmdq_get_command(cmdlist, NULL, NULL, 0);
+			cmdq_append(c, cmdq);
 			cmd_list_free(cmdlist);
 		}
 
diff --git a/format.c b/format.c
index 34d5f796..632dfaf0 100644
--- a/format.c
+++ b/format.c
@@ -492,7 +492,6 @@ struct format_tree *
 format_create(struct cmd_q *cmdq, int flags)
 {
 	struct format_tree	*ft;
-	struct cmd		*cmd;
 
 	if (!event_initialized(&format_job_event)) {
 		evtimer_set(&format_job_event, format_job_timer, NULL);
@@ -510,11 +509,9 @@ format_create(struct cmd_q *cmdq, int flags)
 	format_add_tv(ft, "start_time", &start_time);
 
 	if (cmdq != NULL && cmdq->cmd != NULL)
-		format_add(ft, "command_name", "%s", cmdq->cmd->entry->name);
-	if (cmdq != NULL && cmdq->parent != NULL) {
-		cmd = cmdq->parent->cmd;
-		format_add(ft, "command_hooked", "%s", cmd->entry->name);
-	}
+		format_add(ft, "command", "%s", cmdq->cmd->entry->name);
+	if (cmdq != NULL && cmdq->hook != NULL)
+		format_add(ft, "hook", "%s", cmdq->hook);
 
 	return (ft);
 }
diff --git a/hooks.c b/hooks.c
index 2e0126b7..cdd2cf45 100644
--- a/hooks.c
+++ b/hooks.c
@@ -33,7 +33,6 @@ RB_GENERATE_STATIC(hooks_tree, hook, entry, hooks_cmp);
 
 static struct hook	*hooks_find1(struct hooks *, const char *);
 static void		 hooks_free1(struct hooks *, struct hook *);
-static void		 hooks_emptyfn(struct cmd_q *);
 
 static int
 hooks_cmp(struct hook *hook1, struct hook *hook2)
@@ -140,28 +139,14 @@ hooks_find(struct hooks *hooks, const char *name)
 	return (hook);
 }
 
-static void
-hooks_emptyfn(struct cmd_q *hooks_cmdq)
-{
-	struct cmd_q	*cmdq = hooks_cmdq->data;
-
-	if (cmdq != NULL) {
-		if (hooks_cmdq->client_exit >= 0)
-			cmdq->client_exit = hooks_cmdq->client_exit;
-		if (!cmdq_free(cmdq))
-			cmdq_continue(cmdq);
-	}
-	cmdq_free(hooks_cmdq);
-}
-
-int
+void
 hooks_run(struct hooks *hooks, struct client *c, struct cmd_find_state *fs,
     const char *fmt, ...)
 {
 	struct hook	*hook;
-	struct cmd_q	*hooks_cmdq;
 	va_list		 ap;
 	char		*name;
+	struct cmd_q	*new_cmdq, *loop;
 
 	va_start(ap, fmt);
 	xvasprintf(&name, fmt, ap);
@@ -170,34 +155,30 @@ hooks_run(struct hooks *hooks, struct client *c, struct cmd_find_state *fs,
 	hook = hooks_find(hooks, name);
 	if (hook == NULL) {
 		free(name);
-		return (-1);
+		return;
 	}
 	log_debug("running hook %s", name);
+
+	new_cmdq = cmdq_get_command(hook->cmdlist, fs, NULL, CMD_Q_NOHOOKS);
+
+	for (loop = new_cmdq; loop != NULL; loop = loop->next)
+		loop->hook = xstrdup(name);
 	free(name);
 
-	hooks_cmdq = cmdq_new(c);
-	hooks_cmdq->flags |= CMD_Q_NOHOOKS;
-
-	if (fs != NULL)
-		cmd_find_copy_state(&hooks_cmdq->current, fs);
-	hooks_cmdq->parent = NULL;
-
-	cmdq_run(hooks_cmdq, hook->cmdlist, NULL);
-	cmdq_free(hooks_cmdq);
-	return (0);
+	cmdq_append(c, new_cmdq);
 }
 
-int
-hooks_wait(struct hooks *hooks, struct cmd_q *cmdq, struct cmd_find_state *fs,
+void
+hooks_insert(struct hooks *hooks, struct cmd_q *cmdq, struct cmd_find_state *fs,
     const char *fmt, ...)
 {
 	struct hook	*hook;
-	struct cmd_q	*hooks_cmdq;
 	va_list		 ap;
 	char		*name;
+	struct cmd_q	*new_cmdq, *loop;
 
 	if (cmdq->flags & CMD_Q_NOHOOKS)
-		return (-1);
+		return;
 
 	va_start(ap, fmt);
 	xvasprintf(&name, fmt, ap);
@@ -206,23 +187,18 @@ hooks_wait(struct hooks *hooks, struct cmd_q *cmdq, struct cmd_find_state *fs,
 	hook = hooks_find(hooks, name);
 	if (hook == NULL) {
 		free(name);
-		return (-1);
+		return;
 	}
 	log_debug("running hook %s (parent %p)", name, cmdq);
+
+	new_cmdq = cmdq_get_command(hook->cmdlist, fs, NULL, CMD_Q_NOHOOKS);
+
+	for (loop = new_cmdq; loop != NULL; loop = loop->next)
+		loop->hook = xstrdup(name);
 	free(name);
 
-	hooks_cmdq = cmdq_new(cmdq->client);
-	hooks_cmdq->flags |= CMD_Q_NOHOOKS;
-
-	if (fs != NULL)
-		cmd_find_copy_state(&hooks_cmdq->current, fs);
-	hooks_cmdq->parent = cmdq;
-
-	hooks_cmdq->emptyfn = hooks_emptyfn;
-	hooks_cmdq->data = cmdq;
-
 	if (cmdq != NULL)
-		cmdq->references++;
-	cmdq_run(hooks_cmdq, hook->cmdlist, NULL);
-	return (0);
+		cmdq_insert_after(cmdq, new_cmdq);
+	else
+		cmdq_append(NULL, new_cmdq);
 }
diff --git a/key-bindings.c b/key-bindings.c
index b464c199..af53720c 100644
--- a/key-bindings.c
+++ b/key-bindings.c
@@ -377,18 +377,22 @@ key_bindings_init(void)
 	struct cmd_list	*cmdlist;
 	char		*cause;
 	int		 error;
-	struct cmd_q	*cmdq;
 
-	cmdq = cmdq_new(NULL);
 	for (i = 0; i < nitems(defaults); i++) {
 		error = cmd_string_parse(defaults[i], &cmdlist,
 		    "<default-keys>", i, &cause);
 		if (error != 0)
 			fatalx("bad default key");
-		cmdq_run(cmdq, cmdlist, NULL);
+		cmdq_append(NULL, cmdq_get_command(cmdlist, NULL, NULL, 0));
 		cmd_list_free(cmdlist);
 	}
-	cmdq_free(cmdq);
+}
+
+static enum cmd_retval
+key_bindings_read_only(struct cmd_q *cmdq, __unused void *data)
+{
+	cmdq_error(cmdq, "client is read-only");
+	return (CMD_RETURN_ERROR);
 }
 
 void
@@ -403,10 +407,8 @@ key_bindings_dispatch(struct key_binding *bd, struct client *c,
 		if (!(cmd->entry->flags & CMD_READONLY))
 			readonly = 0;
 	}
-	if (!readonly && (c->flags & CLIENT_READONLY)) {
-		cmdq_error(c->cmdq, "client is read-only");
-		return;
-	}
-
-	cmdq_run(c->cmdq, bd->cmdlist, m);
+	if (!readonly && (c->flags & CLIENT_READONLY))
+		cmdq_append(c, cmdq_get_callback(key_bindings_read_only, NULL));
+	else
+		cmdq_append(c, cmdq_get_command(bd->cmdlist, NULL, m, 0));
 }
diff --git a/notify.c b/notify.c
index 31f23b6f..cca4ff31 100644
--- a/notify.c
+++ b/notify.c
@@ -65,7 +65,7 @@ notify_hook(struct notify_entry *ne)
 	const char		*name;
 	struct cmd_find_state	 fs;
 	struct hook		*hook;
-	struct cmd_q		*hooks_cmdq;
+	struct cmd_q		*new_cmdq, *loop;
 
 	name = notify_hooks[ne->type];
 	if (name == NULL)
@@ -86,14 +86,12 @@ notify_hook(struct notify_entry *ne)
 		return;
 	log_debug("notify hook %s", name);
 
-	hooks_cmdq = cmdq_new(NULL);
-	hooks_cmdq->flags |= CMD_Q_NOHOOKS;
+	new_cmdq = cmdq_get_command(hook->cmdlist, &fs, NULL, CMD_Q_NOHOOKS);
 
-	cmd_find_copy_state(&hooks_cmdq->current, &fs);
-	hooks_cmdq->parent = NULL;
+	for (loop = new_cmdq; loop != NULL; loop = loop->next)
+		loop->hook = xstrdup(name);
 
-	cmdq_run(hooks_cmdq, hook->cmdlist, NULL);
-	cmdq_free(hooks_cmdq);
+	cmdq_append(NULL, new_cmdq);
 }
 
 static void
diff --git a/server-client.c b/server-client.c
index 5697e343..83202a47 100644
--- a/server-client.c
+++ b/server-client.c
@@ -124,8 +124,7 @@ server_client_create(int fd)
 	c->fd = -1;
 	c->cwd = NULL;
 
-	c->cmdq = cmdq_new(c);
-	c->cmdq->client_exit = 1;
+	TAILQ_INIT(&c->queue);
 
 	c->stdin_data = evbuffer_new();
 	c->stdout_data = evbuffer_new();
@@ -242,10 +241,6 @@ server_client_lost(struct client *c)
 	free(c->prompt_string);
 	free(c->prompt_buffer);
 
-	c->cmdq->flags |= CMD_Q_DEAD;
-	cmdq_free(c->cmdq);
-	c->cmdq = NULL;
-
 	environ_free(c->environ);
 
 	proc_remove_peer(c->peer);
@@ -279,6 +274,9 @@ server_client_free(__unused int fd, __unused short events, void *arg)
 
 	log_debug("free client %p (%d references)", c, c->references);
 
+	if (!TAILQ_EMPTY(&c->queue))
+		fatalx("queue not empty");
+
 	if (c->references == 0)
 		free(c);
 }
@@ -1262,6 +1260,29 @@ server_client_dispatch(struct imsg *imsg, void *arg)
 	}
 }
 
+/* Callback when command is done. */
+static enum cmd_retval
+server_client_command_done(struct cmd_q *cmdq, __unused void *data)
+{
+	struct client	*c = cmdq->client;
+
+	if (~c->flags & CLIENT_ATTACHED)
+		c->flags |= CLIENT_EXIT;
+	return (CMD_RETURN_NORMAL);
+}
+
+/* Show an error message. */
+static enum cmd_retval
+server_client_command_error(struct cmd_q *cmdq, void *data)
+{
+	char	*error = data;
+
+	cmdq_error(cmdq, "%s", error);
+	free(error);
+
+	return (CMD_RETURN_NORMAL);
+}
+
 /* Handle command message. */
 static void
 server_client_dispatch_command(struct client *c, struct imsg *imsg)
@@ -1284,7 +1305,7 @@ server_client_dispatch_command(struct client *c, struct imsg *imsg)
 
 	argc = data.argc;
 	if (cmd_unpack_argv(buf, len, argc, &argv) != 0) {
-		cmdq_error(c->cmdq, "command too long");
+		cause = xstrdup("command too long");
 		goto error;
 	}
 
@@ -1295,20 +1316,19 @@ server_client_dispatch_command(struct client *c, struct imsg *imsg)
 	}
 
 	if ((cmdlist = cmd_list_parse(argc, argv, NULL, 0, &cause)) == NULL) {
-		cmdq_error(c->cmdq, "%s", cause);
 		cmd_free_argv(argc, argv);
 		goto error;
 	}
 	cmd_free_argv(argc, argv);
 
-	if (c != cfg_client || cfg_finished)
-		cmdq_run(c->cmdq, cmdlist, NULL);
-	else
-		cmdq_append(c->cmdq, cmdlist, NULL);
+	cmdq_append(c, cmdq_get_command(cmdlist, NULL, NULL, 0));
+	cmdq_append(c, cmdq_get_callback(server_client_command_done, NULL));
 	cmd_list_free(cmdlist);
 	return;
 
 error:
+	cmdq_append(c, cmdq_get_callback(server_client_command_error, cause));
+
 	if (cmdlist != NULL)
 		cmd_list_free(cmdlist);
 
diff --git a/server.c b/server.c
index f7588145..cc6563de 100644
--- a/server.c
+++ b/server.c
@@ -192,9 +192,17 @@ static int
 server_loop(void)
 {
 	struct client	*c;
+	u_int		 items;
+
+	notify_drain();
+
+	do {
+		items = cmdq_next(NULL);
+		TAILQ_FOREACH(c, &clients, entry)
+		    items += cmdq_next(c);
+	} while (items != 0);
 
 	server_client_loop();
-	notify_drain();
 
 	if (!options_get_number(global_options, "exit-unattached")) {
 		if (!RB_EMPTY(&sessions))
diff --git a/tmux.1 b/tmux.1
index c038e68e..7d743e51 100644
--- a/tmux.1
+++ b/tmux.1
@@ -3460,8 +3460,7 @@ The following variables are available, where appropriate:
 .It Li "client_tty" Ta "" Ta "Pseudo terminal of client"
 .It Li "client_utf8" Ta "" Ta "1 if client supports utf8"
 .It Li "client_width" Ta "" Ta "Width of client"
-.It Li "command_hooked" Ta "" Ta "Name of command hooked, if any"
-.It Li "command_name" Ta "" Ta "Name of command in use, if any"
+.It Li "command" Ta "" Ta "Name of command in use, if any"
 .It Li "command_list_name" Ta "" Ta "Command name if listing commands"
 .It Li "command_list_alias" Ta "" Ta "Command alias if listing commands"
 .It Li "command_list_usage" Ta "" Ta "Command usage if listing commands"
@@ -3471,6 +3470,7 @@ The following variables are available, where appropriate:
 .It Li "history_bytes" Ta "" Ta "Number of bytes in window history"
 .It Li "history_limit" Ta "" Ta "Maximum window history lines"
 .It Li "history_size" Ta "" Ta "Size of history in bytes"
+.It Li "hook" Ta "" Ta "Name of running hook, if any"
 .It Li "host" Ta "#H" Ta "Hostname of local host"
 .It Li "host_short" Ta "#h" Ta "Hostname of local host (no domain name)"
 .It Li "insert_flag" Ta "" Ta "Pane insert flag"
diff --git a/tmux.h b/tmux.h
index cf4a3eca..7ced6131 100644
--- a/tmux.h
+++ b/tmux.h
@@ -42,6 +42,8 @@ extern char   **environ;
 
 struct args;
 struct client;
+struct cmd_q;
+struct cmd_q_list;
 struct environ;
 struct input_ctx;
 struct mode_key_cmdstr;
@@ -633,7 +635,6 @@ struct grid {
 struct hook {
 	const char	*name;
 
-	struct cmd_q	*cmdq;
 	struct cmd_list	*cmdlist;
 
 	RB_ENTRY(hook)	 entry;
@@ -1162,101 +1163,6 @@ struct message_entry {
 	TAILQ_ENTRY(message_entry) entry;
 };
 
-/* Client connection. */
-struct client {
-	struct tmuxpeer	*peer;
-
-	pid_t		 pid;
-	int		 fd;
-	struct event	 event;
-	int		 retval;
-
-	struct timeval	 creation_time;
-	struct timeval	 activity_time;
-
-	struct environ	*environ;
-
-	char		*title;
-	const char	*cwd;
-
-	char		*term;
-	char		*ttyname;
-	struct tty	 tty;
-
-	void		(*stdin_callback)(struct client *, int, void *);
-	void		*stdin_callback_data;
-	struct evbuffer	*stdin_data;
-	int		 stdin_closed;
-	struct evbuffer	*stdout_data;
-	struct evbuffer	*stderr_data;
-
-	struct event	 repeat_timer;
-
-	struct event	 click_timer;
-	u_int		 click_button;
-
-	struct event	 status_timer;
-	struct screen	 status;
-
-#define CLIENT_TERMINAL 0x1
-#define CLIENT_LOGIN 0x2
-#define CLIENT_EXIT 0x4
-#define CLIENT_REDRAW 0x8
-#define CLIENT_STATUS 0x10
-#define CLIENT_REPEAT 0x20
-#define CLIENT_SUSPENDED 0x40
-/* 0x80 unused */
-#define CLIENT_IDENTIFY 0x100
-#define CLIENT_DEAD 0x200
-#define CLIENT_BORDERS 0x400
-#define CLIENT_READONLY 0x800
-#define CLIENT_REDRAWWINDOW 0x1000
-#define CLIENT_CONTROL 0x2000
-#define CLIENT_CONTROLCONTROL 0x4000
-#define CLIENT_FOCUSED 0x8000
-#define CLIENT_UTF8 0x10000
-#define CLIENT_256COLOURS 0x20000
-#define CLIENT_IDENTIFIED 0x40000
-#define CLIENT_STATUSFORCE 0x80000
-#define CLIENT_DOUBLECLICK 0x100000
-#define CLIENT_TRIPLECLICK 0x200000
-	int		 flags;
-	struct key_table *keytable;
-
-	struct event	 identify_timer;
-	void		(*identify_callback)(struct client *, struct window_pane *);
-	void		*identify_callback_data;
-
-	char		*message_string;
-	struct event	 message_timer;
-	u_int		 message_next;
-	TAILQ_HEAD(, message_entry) message_log;
-
-	char		*prompt_string;
-	struct utf8_data *prompt_buffer;
-	size_t		 prompt_index;
-	int		 (*prompt_callbackfn)(void *, const char *);
-	void		 (*prompt_freefn)(void *);
-	void		*prompt_data;
-	u_int		 prompt_hindex;
-	enum { PROMPT_ENTRY, PROMPT_COMMAND } prompt_mode;
-
-#define PROMPT_SINGLE 0x1
-#define PROMPT_NUMERIC 0x2
-	int		 prompt_flags;
-
-	struct session	*session;
-	struct session	*last_session;
-
-	int		 wlmouse;
-
-	struct cmd_q	*cmdq;
-	int		 references;
-
-	TAILQ_ENTRY(client) entry;
-};
-TAILQ_HEAD(clients, client);
-
 /* Parsed arguments structures. */
 struct args_entry;
 RB_HEAD(args_tree, args_entry);
@@ -1326,42 +1232,46 @@ enum cmd_retval {
 	CMD_RETURN_STOP
 };
 
-/* Command queue entry. */
-struct cmd_q_item {
-	struct cmd_list		*cmdlist;
-
-	struct mouse_event	 mouse;
-
-	TAILQ_ENTRY(cmd_q_item)	 qentry;
+/* Command queue item type. */
+enum cmd_q_type {
+	CMD_Q_COMMAND,
+	CMD_Q_CALLBACK,
 };
-TAILQ_HEAD(cmd_q_items, cmd_q_item);
 
-/* Command queue. */
+/* Command queue item. */
+typedef enum cmd_retval (*cmd_q_cb) (struct cmd_q *, void *);
 struct cmd_q {
-	int			 references;
-	int			 flags;
-#define CMD_Q_DEAD 0x1
-#define CMD_Q_NOHOOKS 0x2
+	struct cmd_q_list	*queue;
+	struct cmd_q		*next;
 
 	struct client		*client;
-	int			 client_exit;
 
-	struct cmd_q_items	 queue;
-	struct cmd_q_item	*item;
+	enum cmd_q_type		 type;
+	u_int			 group;
+
+	u_int			 number;
+	time_t			 time;
+
+	const char		*hook;
+	int			 flags;
+#define CMD_Q_FIRED 0x1
+#define CMD_Q_WAITING 0x2
+#define CMD_Q_NOHOOKS 0x4
+
+	struct cmd_list		*cmdlist;
 	struct cmd		*cmd;
-	struct cmd_q		*parent;
+
+	cmd_q_cb		 cb;
+	void			*data;
 
 	struct cmd_find_state	 current;
 	struct cmd_state	 state;
 
-	time_t			 time;
-	u_int			 number;
+	struct mouse_event	 mouse;
 
-	void			 (*emptyfn)(struct cmd_q *);
-	void			*data;
-
-	TAILQ_ENTRY(cmd_q)	 waitentry;
+	TAILQ_ENTRY(cmd_q)	 entry;
 };
+TAILQ_HEAD(cmd_q_list, cmd_q);
 
 /* Command -c, -t or -s flags. */
 enum cmd_entry_flag {
@@ -1411,6 +1321,101 @@ struct cmd_entry {
 	enum cmd_retval		 (*exec)(struct cmd *, struct cmd_q *);
 };
 
+/* Client connection. */
+struct client {
+	struct tmuxpeer	*peer;
+	struct cmd_q_list queue;
+
+	pid_t		 pid;
+	int		 fd;
+	struct event	 event;
+	int		 retval;
+
+	struct timeval	 creation_time;
+	struct timeval	 activity_time;
+
+	struct environ	*environ;
+
+	char		*title;
+	const char	*cwd;
+
+	char		*term;
+	char		*ttyname;
+	struct tty	 tty;
+
+	void		(*stdin_callback)(struct client *, int, void *);
+	void		*stdin_callback_data;
+	struct evbuffer	*stdin_data;
+	int		 stdin_closed;
+	struct evbuffer	*stdout_data;
+	struct evbuffer	*stderr_data;
+
+	struct event	 repeat_timer;
+
+	struct event	 click_timer;
+	u_int		 click_button;
+
+	struct event	 status_timer;
+	struct screen	 status;
+
+#define CLIENT_TERMINAL 0x1
+#define CLIENT_LOGIN 0x2
+#define CLIENT_EXIT 0x4
+#define CLIENT_REDRAW 0x8
+#define CLIENT_STATUS 0x10
+#define CLIENT_REPEAT 0x20
+#define CLIENT_SUSPENDED 0x40
+#define CLIENT_ATTACHED 0x80
+#define CLIENT_IDENTIFY 0x100
+#define CLIENT_DEAD 0x200
+#define CLIENT_BORDERS 0x400
+#define CLIENT_READONLY 0x800
+#define CLIENT_REDRAWWINDOW 0x1000
+#define CLIENT_CONTROL 0x2000
+#define CLIENT_CONTROLCONTROL 0x4000
+#define CLIENT_FOCUSED 0x8000
+#define CLIENT_UTF8 0x10000
+#define CLIENT_256COLOURS 0x20000
+#define CLIENT_IDENTIFIED 0x40000
+#define CLIENT_STATUSFORCE 0x80000
+#define CLIENT_DOUBLECLICK 0x100000
+#define CLIENT_TRIPLECLICK 0x200000
+	int		 flags;
+	struct key_table *keytable;
+
+	struct event	 identify_timer;
+	void		(*identify_callback)(struct client *, struct window_pane *);
+	void		*identify_callback_data;
+
+	char		*message_string;
+	struct event	 message_timer;
+	u_int		 message_next;
+	TAILQ_HEAD(, message_entry) message_log;
+
+	char		*prompt_string;
+	struct utf8_data *prompt_buffer;
+	size_t		 prompt_index;
+	int		 (*prompt_callbackfn)(void *, const char *);
+	void		 (*prompt_freefn)(void *);
+	void		*prompt_data;
+	u_int		 prompt_hindex;
+	enum { PROMPT_ENTRY, PROMPT_COMMAND } prompt_mode;
+
+#define PROMPT_SINGLE 0x1
+#define PROMPT_NUMERIC 0x2
+	int		 prompt_flags;
+
+	struct session	*session;
+	struct session	*last_session;
+
+	int		 wlmouse;
+
+	int		 references;
+
+	TAILQ_ENTRY(client) entry;
+};
+TAILQ_HEAD(clients, client);
+
 /* Key binding and key table. */
 struct key_binding {
 	key_code		 key;
@@ -1506,10 +1511,9 @@ void	proc_kill_peer(struct tmuxpeer *);
 
 /* cfg.c */
 extern int cfg_finished;
-extern int cfg_references;
 extern struct client *cfg_client;
 void		 start_cfg(void);
-int		 load_cfg(const char *, struct cmd_q *, int);
+int		 load_cfg(const char *, struct client *, struct cmd_q *, int);
 void		 set_cfg_file(const char *);
 void printflike(1, 2) cfg_add_cause(const char *, ...);
 void		 cfg_print_causes(struct cmd_q *);
@@ -1559,9 +1563,9 @@ void		 hooks_add(struct hooks *, const char *, struct cmd_list *);
 void		 hooks_copy(struct hooks *, struct hooks *);
 void		 hooks_remove(struct hooks *, const char *);
 struct hook	*hooks_find(struct hooks *, const char *);
-int printflike(4, 5) hooks_run(struct hooks *, struct client *,
+void printflike(4, 5) hooks_run(struct hooks *, struct client *,
 		    struct cmd_find_state *, const char *, ...);
-int printflike(4, 5) hooks_wait(struct hooks *, struct cmd_q *,
+void printflike(4, 5) hooks_insert(struct hooks *, struct cmd_q *,
 		    struct cmd_find_state *, const char *, ...);
 
 /* mode-key.c */
@@ -1757,8 +1761,7 @@ char	       **cmd_copy_argv(int, char **);
 void		 cmd_free_argv(int, char **);
 char		*cmd_stringify_argv(int, char **);
 struct cmd	*cmd_parse(int, char **, const char *, u_int, char **);
-int		 cmd_prepare_state(struct cmd *, struct cmd_q *,
-		     struct cmd_q *);
+int		 cmd_prepare_state(struct cmd *, struct cmd_q *);
 char		*cmd_print(struct cmd *);
 int		 cmd_mouse_at(struct window_pane *, struct mouse_event *,
 		     u_int *, u_int *, int);
@@ -1778,16 +1781,15 @@ void		 cmd_list_free(struct cmd_list *);
 char		*cmd_list_print(struct cmd_list *);
 
 /* cmd-queue.c */
-struct cmd_q	*cmdq_new(struct client *);
-int		 cmdq_free(struct cmd_q *);
+struct cmd_q	*cmdq_get_command(struct cmd_list *, struct cmd_find_state *,
+		     struct mouse_event *, int);
+struct cmd_q	*cmdq_get_callback(cmd_q_cb, void *);
+void		 cmdq_insert_after(struct cmd_q *, struct cmd_q *);
+void		 cmdq_append(struct client *, struct cmd_q *);
+u_int		 cmdq_next(struct client *);
+void		 cmdq_guard(struct cmd_q *, const char *, int);
 void printflike(2, 3) cmdq_print(struct cmd_q *, const char *, ...);
 void printflike(2, 3) cmdq_error(struct cmd_q *, const char *, ...);
-void		 cmdq_guard(struct cmd_q *, const char *, int);
-void		 cmdq_run(struct cmd_q *, struct cmd_list *,
-		     struct mouse_event *);
-void		 cmdq_append(struct cmd_q *, struct cmd_list *,
-		     struct mouse_event *);
-int		 cmdq_continue(struct cmd_q *);
 
 /* cmd-string.c */
 int	cmd_string_parse(const char *, struct cmd_list **, const char *,
diff --git a/window-choose.c b/window-choose.c
index 25dc77d1..b7720e8d 100644
--- a/window-choose.c
+++ b/window-choose.c
@@ -246,6 +246,7 @@ window_choose_data_run(struct window_choose_data *cdata)
 {
 	struct cmd_list	*cmdlist;
 	char		*cause;
+	struct cmd_q	*cmdq;
 
 	/*
 	 * The command template will have already been replaced. But if it's
@@ -263,7 +264,8 @@ window_choose_data_run(struct window_choose_data *cdata)
 		return;
 	}
 
-	cmdq_run(cdata->start_client->cmdq, cmdlist, NULL);
+	cmdq = cmdq_get_command(cmdlist, NULL, NULL, 0);
+	cmdq_append(cdata->start_client, cmdq);
 	cmd_list_free(cmdlist);
 }