Enhance proxy for asynchornous handshake on action init (#121)

This commit add an explicit "ack" between the proxy and the action runner. The new protocol requires explicit opt in via environment variables set accordingly. This change allows the proxy to reliabliy detect functions that fail at init time without using timed heuristics (that don't work for slow to come up runtimes, leading to errors at the "run" step instead). The enhancements also better separate logs from the action runtime.
diff --git a/CHANGES.md b/CHANGES.md
index 4af8485..9e0526a 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -16,6 +16,11 @@
 # limitations under the License.
 #
 -->
+# 1.16.0 (next release)
+- added OW_WAIT_FOR_ACK such at if true, the proxy waits for an acknowledgement from the action on startup
+- added OW_EXECUTION_ENV to validate the execution environment before starting an action
+- write compilation logs to standard out
+
 # 1.15.0
 - added OW_ACTION_VERSION to action environment (PR#113)
 - propagate API_HOST from parent to child process (PR#115)
diff --git a/README.md b/README.md
index 960d0a2..7009c42 100644
--- a/README.md
+++ b/README.md
@@ -38,6 +38,7 @@
 - Writing [Generic](docs/ACTION.md#generic) actions, in bash or as a generic linux binary
 - Deployment for [Generic](docs/DEPLOY.md#generic) actions
 - The [ActionLoop](docs/ACTION.md#actionloop) protocol for generic actions
+- Environment [Variables](docs/ENVVARS.md) to configure the proxy
 
 # Change Log
 
@@ -45,4 +46,3 @@
 
 # License
 [Apache 2.0](LICENSE.txt)
-
diff --git a/docs/ACTION.md b/docs/ACTION.md
index 840feb2..8fc639f 100644
--- a/docs/ACTION.md
+++ b/docs/ACTION.md
@@ -75,7 +75,9 @@
 
 ### The Action Loop Protocol
 
-The protocol can be specified informally as follow.
+The protocol can be specified informally as follows.
+
+- Send an acknowledgement after initialization when required. If the environment variable `__OW_WAIT_FOR_ACK` is not empty, write on file descriptor 3 the string `{ "ok": true }`.
 
 - Read one line from standard input (file descriptor 0).
 - Parse the line as a JSON object. Currently the object will be in currently in the format:
@@ -108,6 +110,10 @@
 
 ```bash
 #!/bin/bash
+# send an ack if required
+if test -n "$__OW_WAIT_FOR_ACK"
+  then echo '{"ok":true}' >&3
+fi
 # read input forever line by line
 while read line
 do
diff --git a/docs/ENVVARS.md b/docs/ENVVARS.md
new file mode 100644
index 0000000..22210b1
--- /dev/null
+++ b/docs/ENVVARS.md
@@ -0,0 +1,50 @@
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+-->
+
+# Environment Variables
+
+## Enviroment variables that control behaviour of the proxy
+
+The following variables are usually set in the Dockerfile
+
+`OW_COMPILER` points to the compiler script to use to compile actions.
+
+`OW_SAVE_JAR` enables checking that an upload file is a jar (that is itself a zip file) and it will not expand it if there is a subdirectory names "META-INF" (so it is a jar file). Used to support uploading of Java jars.
+
+`OW_WAIT_FOR_ACK` enables waiting for an acknowledgement in the actionloop protocol. It should be enabled in all the newer runtimes. Do not enable in existing runtimes as it would break existing actions built for that runtime.
+
+`OW_EXECUTION_ENV` enables detection and verification of the compilation environent. The compiler is expected to create a file named `exec.env` in the same folder as the `exec` file to be run. If this variable is set, before starting an action, the init will check that the content of the `exec.env`, trimmed of spaces and new lines, is the same, to ensure an action is executed in the right execution environment.
+
+`OW_LOG_INIT_ERROR` enables logging of compilation error; the default behaviour is to return errors in the answer of the init.
+
+## Environment variables propagated to actions and to the compilation script
+
+The proxy itself sets the following environment variables:
+
+`__OW_EXECUTION_ENV` is the same value that the proxy receive as `OW_EXECUTION_ENV`
+
+`__OW_WAIT_FOR_ACK` is set if the proxy has the variable `OW_WAIT_FOR_ACK` set.
+
+Any other environment variable set in the Dockerfile that starts with `__OW_` are propagated to the proxy and can override also the values set by the proxy.
+
+Furthermore, actions receive their own environment variables and the values set overrides the variables set from the proxy and in the environment.
+
+
+
+
diff --git a/examples/golang-hello-vendor/src/hello/Gopkg.toml b/examples/golang-hello-vendor/src/hello/Gopkg.toml
index 3fc5f20..4dad4be 100644
--- a/examples/golang-hello-vendor/src/hello/Gopkg.toml
+++ b/examples/golang-hello-vendor/src/hello/Gopkg.toml
@@ -17,6 +17,21 @@
 
 # Gopkg.toml example
 #
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
 # Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
 # for detailed Gopkg.toml documentation.
 #
diff --git a/examples/golang-main-vendor/src/main/Gopkg.toml b/examples/golang-main-vendor/src/main/Gopkg.toml
index 48e74a1..c3dfb0e 100644
--- a/examples/golang-main-vendor/src/main/Gopkg.toml
+++ b/examples/golang-main-vendor/src/main/Gopkg.toml
@@ -17,6 +17,21 @@
 
 # Gopkg.toml example
 #
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
 # Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
 # for detailed Gopkg.toml documentation.
 #
diff --git a/openwhisk/_test/badack.sh b/openwhisk/_test/badack.sh
new file mode 100755
index 0000000..524122b
--- /dev/null
+++ b/openwhisk/_test/badack.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+echo "bad ack" >&3
+read
diff --git a/openwhisk/_test/badack2.sh b/openwhisk/_test/badack2.sh
new file mode 100755
index 0000000..0412f00
--- /dev/null
+++ b/openwhisk/_test/badack2.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+echo "{}" >&3
+read
diff --git a/openwhisk/_test/badcompile.sh b/openwhisk/_test/badcompile.sh
new file mode 100755
index 0000000..ef5d986
--- /dev/null
+++ b/openwhisk/_test/badcompile.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+echo "error in stdout"
+echo "error in stderr" >&2
diff --git a/openwhisk/_test/build.sh b/openwhisk/_test/build.sh
index 1922b73..fe17a65 100755
--- a/openwhisk/_test/build.sh
+++ b/openwhisk/_test/build.sh
@@ -57,4 +57,7 @@
 build exec
 test -e exec.zip && rm exec.zip
 zip -q -r exec.zip exec etc dir
+echo exec/env >helloack/exec.env
+zip -j helloack.zip helloack/*
+
 
diff --git a/openwhisk/_test/helloack/exec b/openwhisk/_test/helloack/exec
new file mode 100755
index 0000000..7f14074
--- /dev/null
+++ b/openwhisk/_test/helloack/exec
@@ -0,0 +1,24 @@
+#!/bin/bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+echo '{"ok":true}' >&3
+while read line
+do
+   name="$(echo $line | jq -r .value.name)"
+   echo msg="hello $name"
+   echo '{"hello": "'$name'"}' >&3
+done
diff --git a/openwhisk/actionProxy.go b/openwhisk/actionProxy.go
index 805207c..bceab1a 100644
--- a/openwhisk/actionProxy.go
+++ b/openwhisk/actionProxy.go
@@ -72,6 +72,18 @@
 
 //SetEnv sets the environment
 func (ap *ActionProxy) SetEnv(env map[string]interface{}) {
+	// Propagate proxy version
+	ap.env["__OW_PROXY_VERSION"] = Version
+	// propagate OW_EXECUTION_ENV as  __OW_EXECUTION_ENV
+	ee := os.Getenv("OW_EXECUTION_ENV")
+	if ee != "" {
+		ap.env["__OW_EXECUTION_ENV"] = ee
+	}
+	// require an ack
+	wa := os.Getenv("OW_WAIT_FOR_ACK")
+	if wa != "" {
+		ap.env["__OW_WAIT_FOR_ACK"] = wa
+	}
 	// propagate all the variables starting with "__OW_"
 	for _, v := range os.Environ() {
 		if strings.HasPrefix(v, "__OW_") {
@@ -107,6 +119,20 @@
 		return fmt.Errorf("no valid actions available")
 	}
 
+	// check version
+	execEnv := os.Getenv("OW_EXECUTION_ENV")
+	if execEnv != "" {
+		execEnvFile := fmt.Sprintf("%s/%d/bin/exec.env", ap.baseDir, highestDir)
+		execEnvData, err := ioutil.ReadFile(execEnvFile)
+		if err != nil {
+			return err
+		}
+		if strings.TrimSpace(string(execEnvData)) != execEnv {
+			fmt.Printf("Expected exec.env should start with %s\nActual value: %s", execEnv, execEnvData)
+			return fmt.Errorf("Execution environment version mismatch. See logs for details.")
+		}
+	}
+
 	// save the current executor
 	curExecutor := ap.theExecutor
 
@@ -115,7 +141,9 @@
 	os.Chmod(executable, 0755)
 	newExecutor := NewExecutor(ap.outFile, ap.errFile, executable, ap.env)
 	Debug("starting %s", executable)
-	err := newExecutor.Start()
+
+	// start executor
+	err := newExecutor.Start(os.Getenv("OW_WAIT_FOR_ACK") != "")
 	if err == nil {
 		ap.theExecutor = newExecutor
 		if curExecutor != nil {
diff --git a/openwhisk/actionProxy_test.go b/openwhisk/actionProxy_test.go
index 2fdbd06..e28772c 100644
--- a/openwhisk/actionProxy_test.go
+++ b/openwhisk/actionProxy_test.go
@@ -126,6 +126,24 @@
 	// ./action/c2/out/exec
 }
 
+func Example_badcompile() {
+
+	os.Setenv("OW_LOG_INIT_ERROR", "1")
+	ts, cur, log := startTestServer("_test/badcompile.sh")
+	res, _, _ := doPost(ts.URL+"/init", initBytes([]byte("hello"), "main"))
+	fmt.Print(res)
+	stopTestServer(ts, cur, log)
+	os.Setenv("OW_LOG_INIT_ERROR", "")
+	// Unordered output:
+	// {"error":"The action failed to generate or locate a binary. See logs for details."}
+	// error in stdout
+	// error in stderr
+	//
+	// XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
+	// XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
+
+}
+
 func Example_SetEnv() {
 	ap := NewActionProxy("", "", nil, nil)
 	fmt.Println(ap.env)
@@ -144,3 +162,31 @@
 	// [1,2,3] {"a":1,"b":2} string 123
 
 }
+
+func Example_executionEnv_nocheck() {
+	os.Setenv("OW_EXECUTION_ENV", "")
+	ts, cur, log := startTestServer("")
+	res, _, _ := doPost(ts.URL+"/init", initBinary("_test/helloack.zip", "main"))
+	fmt.Print(res)
+	stopTestServer(ts, cur, log)
+	// Output:
+	// {"ok":true}
+}
+
+func Example_executionEnv_check() {
+	os.Setenv("OW_EXECUTION_ENV", "bad/env")
+	ts, cur, log := startTestServer("")
+	res, _, _ := doPost(ts.URL+"/init", initBinary("_test/helloack.zip", "main"))
+	fmt.Print(res)
+	os.Setenv("OW_EXECUTION_ENV", "exec/env")
+	res, _, _ = doPost(ts.URL+"/init", initBinary("_test/helloack.zip", "main"))
+	fmt.Print(res)
+	stopTestServer(ts, cur, log)
+	// reset value
+	os.Setenv("OW_EXECUTION_ENV", "")
+	// Output:
+	// Expected exec.env should start with bad/env
+	// Actual value: exec/env
+	// {"error":"cannot start action: Execution environment version mismatch. See logs for details."}
+	// {"ok":true}
+}
diff --git a/openwhisk/compiler.go b/openwhisk/compiler.go
index 686c5e2..0236be4 100644
--- a/openwhisk/compiler.go
+++ b/openwhisk/compiler.go
@@ -54,15 +54,18 @@
 	var cmd *exec.Cmd
 	cmd = exec.Command(ap.compiler, main, srcDir, binDir)
 	cmd.Env = []string{"PATH=" + os.Getenv("PATH")}
+	for k, v := range ap.env {
+		cmd.Env = append(cmd.Env, k+"="+v)
+	}
 
 	// gather stdout and stderr
 	out, err := cmd.CombinedOutput()
 	Debug("compiler out: %s, %v", out, err)
-	if err != nil {
-		return err
-	}
 	if len(out) > 0 {
 		return fmt.Errorf("%s", out)
 	}
+	if err != nil {
+		return err
+	}
 	return nil
 }
diff --git a/openwhisk/executor.go b/openwhisk/executor.go
index 707018d..2744738 100644
--- a/openwhisk/executor.go
+++ b/openwhisk/executor.go
@@ -19,6 +19,8 @@
 
 import (
 	"bufio"
+	"encoding/json"
+	"errors"
 	"fmt"
 	"io"
 	"os"
@@ -79,7 +81,26 @@
 	// input to the subprocess
 	proc.input.Write(in)
 	proc.input.Write([]byte("\n"))
-	out, err := proc.output.ReadBytes('\n')
+
+	chout := make(chan []byte)
+	go func() {
+		out, err := proc.output.ReadBytes('\n')
+		if err == nil {
+			chout <- out
+		} else {
+			chout <- []byte{}
+		}
+	}()
+	var err error
+	var out []byte
+	select {
+	case out = <-chout:
+		if len(out) == 0 {
+			err = errors.New("no answer from the action")
+		}
+	case <-proc.exited:
+		err = errors.New("command exited")
+	}
 	proc.cmd.Stdout.Write([]byte(OutputGuard))
 	proc.cmd.Stderr.Write([]byte(OutputGuard))
 	return out, err
@@ -95,10 +116,16 @@
 	}
 }
 
+// ActionAck is the expected data structure for the action acknowledgement
+type ActionAck struct {
+	Ok bool `json:"ok"`
+}
+
 // Start execution of the command
-// wait a bit to check if the command exited
+// if the flag ack is true, wait forever for an acknowledgement
+// if the flag ack is false wait a bit to check if the command exited
 // returns an error if the program fails
-func (proc *Executor) Start() error {
+func (proc *Executor) Start(waitForAck bool) error {
 	// start the underlying executable
 	Debug("Start:")
 	err := proc.cmd.Start()
@@ -108,15 +135,54 @@
 		return fmt.Errorf("command exited")
 	}
 	Debug("pid: %d", proc.cmd.Process.Pid)
+
 	go func() {
 		proc.cmd.Wait()
 		proc.exited <- true
 	}()
+
+	// not waiting for an ack, so use a timeout
+	if !waitForAck {
+		select {
+		case <-proc.exited:
+			return fmt.Errorf("command exited")
+		case <-time.After(DefaultTimeoutStart):
+			return nil
+		}
+	}
+
+	// wait for acknowledgement
+	Debug("waiting for an ack")
+	ack := make(chan error)
+	go func() {
+		out, err := proc.output.ReadBytes('\n')
+		Debug("received ack %s", out)
+		if err != nil {
+			ack <- err
+			return
+		}
+		// parse ack
+		var ackData ActionAck
+		err = json.Unmarshal(out, &ackData)
+		if err != nil {
+			ack <- err
+			return
+		}
+		// check ack
+		if !ackData.Ok {
+			ack <- fmt.Errorf("The action did not initialize properly.")
+			return
+		}
+		ack <- nil
+	}()
+	// wait for ack or unexpected termination
 	select {
+	// ack received
+	case err = <-ack:
+		return err
+	// process exited
 	case <-proc.exited:
-		return fmt.Errorf("command exited")
-	case <-time.After(DefaultTimeoutStart):
-		return nil
+		return fmt.Errorf("command exited before ack")
 	}
 }
 
diff --git a/openwhisk/executor_test.go b/openwhisk/executor_test.go
index 93702ad..bae8962 100644
--- a/openwhisk/executor_test.go
+++ b/openwhisk/executor_test.go
@@ -26,19 +26,19 @@
 func ExampleNewExecutor_failed() {
 	log, _ := ioutil.TempFile("", "log")
 	proc := NewExecutor(log, log, "true", m)
-	err := proc.Start()
+	err := proc.Start(false)
 	fmt.Println(err)
 	proc.Stop()
 	proc = NewExecutor(log, log, "/bin/pwd", m)
-	err = proc.Start()
+	err = proc.Start(false)
 	fmt.Println(err)
 	proc.Stop()
 	proc = NewExecutor(log, log, "donotexist", m)
-	err = proc.Start()
+	err = proc.Start(false)
 	fmt.Println(err)
 	proc.Stop()
 	proc = NewExecutor(log, log, "/etc/passwd", m)
-	err = proc.Start()
+	err = proc.Start(false)
 	fmt.Println(err)
 	proc.Stop()
 	// Output:
@@ -51,7 +51,7 @@
 func ExampleNewExecutor_bc() {
 	log, _ := ioutil.TempFile("", "log")
 	proc := NewExecutor(log, log, "_test/bc.sh", m)
-	err := proc.Start()
+	err := proc.Start(false)
 	fmt.Println(err)
 	res, _ := proc.Interact([]byte("2+2"))
 	fmt.Printf("%s", res)
@@ -67,7 +67,7 @@
 func ExampleNewExecutor_hello() {
 	log, _ := ioutil.TempFile("", "log")
 	proc := NewExecutor(log, log, "_test/hello.sh", m)
-	err := proc.Start()
+	err := proc.Start(false)
 	fmt.Println(err)
 	res, _ := proc.Interact([]byte(`{"value":{"name":"Mike"}}`))
 	fmt.Printf("%s", res)
@@ -84,7 +84,7 @@
 func ExampleNewExecutor_env() {
 	log, _ := ioutil.TempFile("", "log")
 	proc := NewExecutor(log, log, "_test/env.sh", map[string]string{"TEST_HELLO": "WORLD", "TEST_HI": "ALL"})
-	err := proc.Start()
+	err := proc.Start(false)
 	fmt.Println(err)
 	res, _ := proc.Interact([]byte(`{"value":{"name":"Mike"}}`))
 	fmt.Printf("%s", res)
@@ -96,3 +96,54 @@
 	// XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
 	// XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
 }
+
+func ExampleNewExecutor_ack() {
+	log, _ := ioutil.TempFile("", "log")
+	proc := NewExecutor(log, log, "_test/hi", m)
+	err := proc.Start(true)
+	fmt.Println(err)
+	proc.Stop()
+	dump(log)
+	// Output:
+	// command exited before ack
+	// hi
+}
+
+func ExampleNewExecutor_badack() {
+	log, _ := ioutil.TempFile("", "log")
+	proc := NewExecutor(log, log, "_test/badack.sh", m)
+	err := proc.Start(true)
+	fmt.Println(err)
+	proc.Stop()
+	dump(log)
+	// Output:
+	// invalid character 'b' looking for beginning of value
+}
+
+func ExampleNewExecutor_badack2() {
+	log, _ := ioutil.TempFile("", "log")
+	proc := NewExecutor(log, log, "_test/badack2.sh", m)
+	err := proc.Start(true)
+	fmt.Println(err)
+	proc.Stop()
+	dump(log)
+	// Output:
+	// The action did not initialize properly.
+}
+
+func ExampleNewExecutor_helloack() {
+	log, _ := ioutil.TempFile("", "log")
+	proc := NewExecutor(log, log, "_test/helloack/exec", m)
+	err := proc.Start(true)
+	fmt.Println(err)
+	res, _ := proc.Interact([]byte(`{"value":{"name":"Mike"}}`))
+	fmt.Printf("%s", res)
+	proc.Stop()
+	dump(log)
+	// Output:
+	// <nil>
+	// {"hello": "Mike"}
+	// msg=hello Mike
+	// XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
+	// XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
+}
diff --git a/openwhisk/initHandler.go b/openwhisk/initHandler.go
index 0664508..1391c63 100644
--- a/openwhisk/initHandler.go
+++ b/openwhisk/initHandler.go
@@ -117,14 +117,28 @@
 	// if a compiler is defined try to compile
 	_, err = ap.ExtractAndCompile(&buf, main)
 	if err != nil {
-		sendError(w, http.StatusBadGateway, err.Error())
+		if os.Getenv("OW_LOG_INIT_ERROR") == "" {
+			sendError(w, http.StatusBadGateway, err.Error())
+		} else {
+			ap.errFile.Write([]byte(err.Error() + "\n"))
+			ap.outFile.Write([]byte(OutputGuard))
+			ap.errFile.Write([]byte(OutputGuard))
+			sendError(w, http.StatusBadGateway, "The action failed to generate or locate a binary. See logs for details.")
+		}
 		return
 	}
 
 	// start an action
 	err = ap.StartLatestAction()
 	if err != nil {
-		sendError(w, http.StatusBadRequest, "cannot start action: "+err.Error())
+		if os.Getenv("OW_LOG_INIT_ERROR") == "" {
+			sendError(w, http.StatusBadGateway, "cannot start action: "+err.Error())
+		} else {
+			ap.errFile.Write([]byte(err.Error() + "\n"))
+			ap.outFile.Write([]byte(OutputGuard))
+			ap.errFile.Write([]byte(OutputGuard))
+			sendError(w, http.StatusBadGateway, "Cannot start action. Check logs for details.")
+		}
 		return
 	}
 	ap.initialized = true
diff --git a/openwhisk/initHandler_test.go b/openwhisk/initHandler_test.go
index e18edd7..89e9545 100644
--- a/openwhisk/initHandler_test.go
+++ b/openwhisk/initHandler_test.go
@@ -221,9 +221,9 @@
 	// Output:
 	// 500 {"error":"no action defined yet"}
 	// 403 {"error":"Missing main/no code to execute."}
-	// 400 {"error":"cannot start action: command exited"}
-	// 400 {"error":"cannot start action: command exited"}
-	// 400 {"error":"cannot start action: command exited"}
+	// 502 {"error":"cannot start action: command exited"}
+	// 502 {"error":"cannot start action: command exited"}
+	// 502 {"error":"cannot start action: command exited"}
 	// 500 {"error":"no action defined yet"}
 	// hi
 }
diff --git a/openwhisk/version.go b/openwhisk/version.go
index 2bc050c..fd310fe 100644
--- a/openwhisk/version.go
+++ b/openwhisk/version.go
@@ -17,4 +17,4 @@
 package openwhisk
 
 // Version number - internal
-var Version = "1.14.0"
+var Version = "1.16.0"