Enable shift+enter in Claude Code

This is yet another "thing I will forget, so I'm writing a note here" post.

I've been used to using Claude Code inside the IntelliJ plugin.  That plugin is very basic and still in beta, but it works well enough.  All it really does is run Claude Code in the integrated terminal, provide the current file as context, and show changes in the integrated diff viewer.  But it also pre-configures the setting to allow you to press shift+enter in the Claud Code to enter a newline.

If you fire up Claude Code in a Windows Terminal session, this does not work.  To enter a newline, you have to type a backslash followed by enter, which sort of makes sense, but is annoying and unintuitive.  There is a /terminal-setup command, but that also doesn't work in Windows Terminal.  Apparently Anthropic hasn't gotten around to implementing that yet.

Fortunately, there is a work-around.  You can manually add the key bindings to your Windows Terminal configuration.  

"actions": [
  {
    "command": {
      "action": "sendInput",
      "input": "\u000A"
    },
    "id": "User.sendLF"
  }
],
"keybindings": [
  {
    "id": "User.sendLF",
    "keys": "shift+enter"
  }
]

That's what Claude spit out from a scape of Github, anyway.  I originally got it from a Redit post that said essentially the same thing, but now I can't find it.  Either way, it seems to work just fine.

Disabling the terminal in vim-ps1

This is another one of those "note to myself because I can never remember this" posts. 

For quite a while, I've been using the vim-ps1 plugin for Powershell syntax highlighting.  It's generally fine, but I have one annoyance with it: When I open a Powershell file, the plugin opens a horizontal split with a Powershell terminal in it.  That's...fine, but most of the time I don't want that.  And the documentation doesn't say anything about it.

Well, fortunately there's a resolved Github issue with the answer.  Turns out the issue isn't actually with vim-ps1, it's with coc-powershell, which actually does mention this issue in the docs.  Why the language server is popping up a terminal window isn't clear to me, but the solution is to add this to your coc-settings.json file:
"powershell.integratedConsole.showOnStartup": false

Easy fix!  Unfortunately, I can't check that into my vim-config Git repo because I have my Intelephense license key in that file, so I need to document it someplace else.  Hence this post.

As an aside, before scouring the web for that answer, I tried asking Claude.  That didn't go well.  It's first answer was to "look at your Vim config" or try disabling plugins, which was not even remotely useful.  When I pointed that I knew there was a setting for this, it asked me where I saw it.  If I remembered that, I wouldn't need to ask the AI!  At that point, I decided it was easier to do this the old-fashioned way and ended up checking the Github issues and found the solution in a couple of minutes.

Things like that make me appreciate William Bernstein's comments in his interview with Rob Berger.  When asked about using AI in his writing and research, he said that AI is extremely useful, but it's kind of like having a very dumb graduate assistant.  That seems about right to me.  Even a dumb grad student is still smart enough to get into grad school, so they're capable of doing some very helpful work, but they still have some significant limitations.

Using a docker proxy in WSL

I recently started working on a web-based project that has some non-standard networking stuff going on in the dev environment.  The fix was easy, but took a little searching, hence this post.

The codebase itself is built on Node JS, but I have to run it in WSL.  There are various build issues that keep it from building cleanly in Windows and it would be a bit of work to fix that.  But it builds just fine under WSL, so it's easier to use that.

The dev setup uses a Docker container running HAProxy.  It's set up to proxy the static portion of the website to the shared dev server (which is on the VPN) and direct the traffic for the dynamic part to a local port.  So we have a HAProxy running in Docker, the local dev server running in WSL, and the browser running in Windows.

The problem: In the default WSL config, this doesn't work.  The proxy to the shared server works just fine.  So if I go to wwwlocal.site.com, that works as a proxy to wwwdev.site.com.  Perfect!  However, wwwlocal.site.com/login is supposed to proxy to localhost:4321, but that results in a 503 error.  However, I can go directly to localhost:4321 and it works just fine, so this is clearly a networking issue.

The solution was to just add a few lines to my $home\.wslconfig file on Windows.  The following lines did the trick:

[wsl2]
networkingMode=mirrored
dnsTunneling=true
autoProxy=true

You can read more about these settings here.

SSH Agent with Powershell

Another "note to my future self", recoded here for posterity.

Today I finally got around to making SSH agent work in Powershell with Git.  For the last year or so, I haven't had to deal with because my work mostly involved writing PHP code inside of WSL.  In that scenario, you're essentially using Linux, so ssh-agent works just fine.

But on native Windows with Powershell...not so much.

I mean, sure, I could just use Git BASH.  But why would I want to do that?  Powershell is awesome, and even if I probably know BASH better, I prefer Powershell.

But it turns out it wasn't all that difficult to get things working in Powershell.  There were two pieces to it:

  1. Use the right SSH agent (i.e. the one that comes with Git).
  2. Write a Powershell adapter for it.

It turns out that there's a Windows service for "OpenSSH Authentication Agent".  I'm not entirely sure what installs that, but apparently it's different from the ssh-agent that's installed with the Windows Git package and the ssh-add command from that doesn't seem to talk to it properly.

My solution was to just disable that service and use the plain-old ssh-agent that comes with Git.  The only problem with that is that the traditional invocation of eval `ssh-agent` doesn't work in Powershell because it outputs a BASH-formatted script.  But that's easily fixed with a couple of regular expressions.  So I added this to my Powershell $profile:

Set-Alias ssh-agent "$env:ProgramFiles\git\usr\bin\ssh-agent.exe"
Set-Alias ssh-add "$env:ProgramFiles\git\usr\bin\ssh-add.exe"

# Need to turn off Open SSH Authentication Agent system service,
# then run the ssh-agent from Git.
function Start-SshAgent {
    $output = (ssh-agent)
    $sock_match = ($output | Select-String -Pattern 'SSH_AUTH_SOCK=(\S+);')
    $sock_path = $sock_match[0].Matches.Groups[1].Value
    $pid_match = ($output | Select-String -Pattern 'SSH_AGENT_PID=(\d+);')
    $agent_pid = $pid_match[0].Matches.Groups[1].Value
    $env:SSH_AUTH_SOCK = $sock_path
    $env:SSH_AGENT_PID = $agent_pid
    Write-Output "Agent pid $agent_pid"
}

Start-SshAgent

And there we go!  Now I can just run ssh-add to add my key and Git picks it up as expected.

Minor WSL GUI annoyance

Minor annoyance: For some reason, WSL GUI windows like to hide themselves.  I'm not sure why.

I see this all the time on my Windows 10 work laptop, where I use WSL extensively.  It usually happens after my laptop has gone to sleep.  I come back in the morning, log in, and all of my open WSL gVim windows are just gone.  The processes are still running, they're just not displayed and they're MIA from the Windows taskbar.

Fortunately, this is easily fixed by just starting a new WSL GUI app.  So I generally just pop up my Windows Terminal and start a new gVim instance, and then the running instances pop back into existence and they're fine until the system sleeps again.

I have no idea why that happens, nor if it's specific to Windows 10 or anything else.  It's just one of those things that's annoying but not a big deal.  Rant over.

Sometimes WSL needs an update, apparently

Note to self: Sometimes, it seems that WSL needs to be updated.  I mean, yes, obviously it does.  But apparently sometimes Windows updates will break it and you need to manually intervene.

I saw this the other day.  At some point, something got updated and when I tried to open a new WSL terminal, it would always fail with the message Error code: Wsl/Service/0x80040326.  Oddly enough, the WSL shells and processes I already had running still worked, I just couldn't start any new ones.

Luckily, the fix is pretty simple.  You just have to open up a Powershell instance run wsl --update to update WSL and then wsl --shutdown to reboot it.  That's it.  It's just annoying because that's a manual process - you still need to manually run the update, even if you reboot your computer.

Nextcloud session annoyances

This is a note to my future self about an annoyance with Nextcloud.  If you're not aware of it, Nextcloud is basically a fork of ownCloud, which is a "self-hosted cloud" platform.  Basically, they both provide a bunch of cloud-based services, like file sync and share, calendar, contacts, and various other things.  I switched to Nextcloud last year because ownCloud was lagging way behind in its support for newer PHP versions.

Anyway, I noticed a rather annoying issue where Nextcloud was leaving hundreds of stale auth tokens in the database.  Apparently, I'm not the only person this has happened to.

While Nextcloud has a menu item to revoke and remove stale sessions on their settings page, it's on a per-item basis.  So if you have hundreds of stale sessions, the only way to remove them is to go through, one by one, and click the menu and select the "revoke" option.  Needless to say, this is terrible.

The less annoying solution is to just go straight into the database and delete them there.  You can just run something like:
DELETE FROM oc_authtoken WHERE last_activity < <whatever_timestamp>;
That might be ugly, but at least it doesn't take forever.

It's important to note that, in addition to being annoying, this is evidently also a performance problem.  From what I've read, it's the reason that authenticating to my Nextcloud instance had gotten absurdly slow.  The app responded fine once I was logged in, but the login process itself took forever.  It also seems to be the reason why my hosting provider's control panel has been showing I'm way over my allotted MySQL execution time.  After deleting all those stale sessions, not only is login nice and snappy again, but my MySQL usage dropped off a ledge.  Just look at this graph:

2023-02-21T17-16-56-020Z-med.png

As you can see, January is a sea of red, and then it drops off to be comfortably under the limit after I deleted the old sessions.  The Nextcloud team really needs to fix this issue.

Non-standard project setup with CoC and Pyright

Here's a quick little thing that I'll probably need to remember and might be useful to other.

At work, we have a PHP project that has automated integration tests written in Python.  The top-level directory of the Git repo is the normal PHP stuff, but the tests/integration/ directory is a Python sub-project that uses Poetry and Pytest.

Now, I use Vim as my editor and CoC with Intelliphense and Pyright for my PHp and Pythong language servers.  Since the main repo is PHP, Intelliphense works just fine.  Hwoever, Pyright needs a little help.  In particular, it didn't find the third-party dependencies or the integration test framework packages because it didn't know where to look.

Fortunately, this is easily fixed by creating a pyrightconfig.json file.  I was able to create one of those in the top-level directory of the project and add an "execution environment" to tell Pyright where to find the root of the Python project.  I set it to the "tests" directory because, while the main dir is tests/integration/,  that directory is also a Python module, so using "tests" lets Pyright find the "integration" module.

My particular file looks like this:

{
    "venvPath": "tests/integration",
    "venv": ".venv",
    "executionEnvironments": [
        {
            "root": "tests
        }
    ]
}

Composer autoload annoyances

Note to self: Sometimes you need to run composer du -o to make things work.

I'm not entirely sure why.  But it's a pain in the butt.

This has come up a couple of times in working on one of our projects for work.  This particular one is an internal command-line application written in PHP and using the command-line components from the Symfony framework.  I won't get into the details, but the point is that it glues together the various steps required to configure and run certain things on Linux-based servers.  So to test it, I have to put the code on my test server and make sure it works there.

The problem that I ran into today was that I tried to add a new command class to the application and it barfed all over itself.  The Symfony DI container complained that it couldn't find a certain class name in a certain file. The PSR-4 autoloader requires that the class name and namespace match the filesystem path, so usually this indicates a typo in one of those.  But in this case, everything was fine.  The app worked fine on my laptop, and if I deleted the new command, it worked again.

Well, it turns out that running composer du -o fixed it.  I suspect, based on the composer documentation, that the issue was that the class map was being cached by the opcache.  The Symfony cache was empty, so that's about the only one left.  Unfortunately, this is pretty opaque when it comes to trouble-shooting.  I would have expected it to fall back to reading the filesystem, but apparently there's more to it than that.  Perhaps it's related to how Symfony collects the commands - I haven't put in the time to investigate it.

But in any case, that's something to look out for.  Gotta love those weird errors that give you absolutely no indication of the solution.

Changing the display manager in Ubuntu

Just as a quick note to my future self, if you want to change the display manager in Ubuntu, you just need to run the following:

$ dpkg-reconfigure gdm3
$ systemctl restart display-manager.service

The reconfigure will bring up a menu that allows you to choose from the installed display managers.  There's a nice summary with pictures here.

If anyone cares, the context here is that I run Ubuntu 20.04 with the Trinity Desktop Environment on my home destkop/server.  The problem is that pretty much every time I run an upgrade on that box it resets the display manager from TDM back to the default GDM. 

This is actually a really big problem because GDM doesn't work on this box.  Aside from the disk drives, most of the hardware in this box is about 10 years old.  So the video card is sufficiently archaic that GNOME just can't deal with.  When I try to log into GNOME or even just use GDM, I end up with massive display corruption and the desktop is basically unusable.  

One of these days, I should really replace that computer.  Or maybe rebuild it.  Or possibly just relegate it to purely headless server duty and get a different box to use as a desktop.  One day....

USB drive repair

This week, a note to my future self: undoing a USB drive that was flashed with a bootable image is a pain in the neck.

This week my wife wanted a USB thumb drive so she could take some documents to her mother's house and print them out using her printer, because ours is a pain in the neck and it's out of ink.  Well, the good news is that I had two drives handy!  The bad news is that neither of them did anything when I plugged them into her computer.

Of course, it turns out that, at some point, I'd used both of these as bootable drives for a something Linux-based.  This means, of course, that Windows can't read the partitions, so it can't write files to them.  In fact, when I plugged the drives in, nothing happened - they didn't even show up in Explorer.  So I had to fix them.

Well, the first one was pretty easy.  I just used the "Disk Management" console (a.k.a. diskmgmt.msc).  Using that, I was able to see the disk and it's partitions.  It was then a simple matter of deleting the existing partitions, creating a new one, and formatting it.  Then Windows found the drive just fine.

That didn't work so well for the second disk.  The drive showed up with an EFI partition and a lot of unallocated space.  I tried to create a partition in that space, but it didn't work.  So I eventually ended up downloading Rufus.  This is a handy tool that I've used in the past to image USB drives with ISO images.  Well, it can also do a plain-old reformat of a drive.  I just selected "unbootable" for the boot image, GPT for the partition type, and told it to format.  Rufus successfully blew away the entire drive and gave me back a fresh, working USB drive.

So the process wasn't too bad.  It's just a matter of realizing what's going on and getting an appropriate tool to fix it.

Coronavirus info for fellow New Yorkers

For my own future reference, and for the benefit of any fellow residents of New York State, here are a few links to some useful information on coronavirus cases.

The thing I like about these pages is that they're broken down by county.  We all know that the number of cases and infection rates vary by state, but it's important to remember that they also vary within the state.  This is useful for judging the relative risk of activities based on where you are.

In general, the more rural counties have lower infection rates than those with major cities.  In fact, the virus is practically absent from some of the more rural counties.  Does this mean you can just ignore the pandemic?  No.  But it's useful information to include in your risk assessments.  Maybe knowing that the infection rate in your area is very low will take some weight off your mind.  Or maybe you can take a vacation or a long weekend in a county with a very low rate and do some of those activities that you might not feel comfortable with at home.

But that's just my two cents.  As always with the pandemic, decisions have trade-offs and we're operating with incomplete information.  All we can do is to try our best to think clearly, assess the risks and rewards of the choices available to us, and make the best decision we can with what we have at the time.

Database URIs are a pain

Note to self: When using SQLite with SQLAlchemy, the URI has three slashes after the colon, and that's not counting a leading slash.

I only post this because I've forgotten this at least three times and had to spend way too much time figuring it out.  A have a project that has a test SQLite database in the local project directory with a URI of sqlite:///../file.db.  And that's fine.  But then I forget and try to change it to an absolute path and can't figure out why sqlite:///path/to/file.db doesn't work.  But of course that's wrong: it's sqlite:////path/to/file.db with four slashes at the beginning - the last one being part of the actual path.  Hopefully this time I won't forget.

Execution policy weirdness

I discovered something enlightening and annoying this morning: PowerShell uses different execution policy settings for the 32-bit and 64-bit versions.  This is something I did not know and did not expect.

I'd seen this problem before, but could never figure out what was happening.  I'd try to run a command through PowerShell using another program, in this case Vim, and it would fail with the standard error about execution of my profile.ps1 being prohibited by the execution policy setting.  Yet when I bring up Powershell on the command line, everything works and the execution policy looks correct:

PS pgeer> Get-ExecutionPolicy -List Scope ExecutionPolicy ----- --------------- MachinePolicy Undefined UserPolicy Undefined Process Undefined CurrentUser Undefined LocalMachine RemoteSigned

Well, it turns out we're talking about two different versions of Powershell.  When I run it from the terminal, I'm using the 64-bit version.  But my Vim build is 32-bit, which apparently means that it's running the 32-bit version of Powershell.  And that version uses its own execution policy setting.  You can confirm that by explicitly opening "Windows Powershell (x86)" from the start menu and running Get-ExecutionPolicy.  

As for the fix, it's simple: just run Set-ExecutionPolicy RemoteSigned as admin, but do it in both the x86 and x64 versions of Powershell.

As for why Microsoft chose to make the different builds use different execution policies, I couldn't say.  It doesn't make much sense to me.  Although, to be honest, I'm not sure why I even need both versions on the same system.  Maybe to support interop between commandlets and unmanaged code?  Who knows?

Powershell version of "which" command

As yet another note to myself (because I keep having to look this up), the equivalent of the UNIX which command in PowerShell is Get-Command.  That commandlet will let you pass in a name, with no extension, and return you the executable in your path that corresponds to that name.  This is really handy because it works with any executable, including native binaries and batch and PowerShell scripts.  So, for example:

PS pgeer> Get-Command rsync

CommandType     Name                 Version    Source
-----------     ----                 -------    ------
Application     rsync.cmd            0.0.0.0    C:\Users\pgeer\bin\rsync.cmd

Making Windows Git Do SSH

Another quick note to my future self: setting up Git under Windows to use SSH key authentication is pretty easy...once you know what to do.

At work, we have some Composer and Bower packages that we fetch from our internal GitHub Enterprise server.  And, of course, all the source lines in the composer.json and bower.json files use SSH references.  I just use HTTP for my code checkouts, so I finally had to figure out how to make Git for Windows authenticate with my SSH key.

Turns out it's pretty easy.  I have a key pair I created with PuTTYgen.  All I had to do was export my private key to OpenSSH format and copy that file to C:\Users\<my_username>\.ssh\id_rsa.  Git picks it up with no further configuration.  Then I just added my key to GitHub and I was good to go.

Using PuTTY inside ConEmu

This is yet another one of those "post this so I can refer back to it later" things.  So if you're not a Windows user, or if you don't use ConEmu, then I suggest you go get a cup of coffee or something.

So for a while now I've been using ConEmu as my Windows console app.  It supports multiple tabs, transparency (ooooh), customizable hotkeys, customizable sessions, Far manager integration and a whole bunch of other nifty stuff.  

A couple of months ago, I saw that it was possible to embed PuTTY, the popular Windows-based SSH client, directly in a ConEmu tab.  So I tried it out and found it to be pretty slick.  The only down side was some key binding weirdness.

First, there's the general putty issue that if you accidentally press ctrl+S - you know, the key combination that means "save" in just about every editor in existence - it effectively locks the terminal and it's not obvious how to get control back.  The second issue is that, when you embed an application like PuTTY in ConEmu, it steals most of your keyboard input, so the standard key bindings for switching between window tabs don't work.

Luckily, these problems are easily fixed.  The fixes are just hard to remember, which is why I'm writing it down.  For the ctrl+S iissue, you can just hit ctrl+Q to turn input back on.  For the tab-switching issue, you can use the global key bindings for ConEmu - namely Win+Q, Win+Shift+Q, and Win+<Number> to switch consoles, as well as Win+Z to toggle focus between ConEmu and PuTTY.

 

Fixing Lenovo IdeaPad function keys

Note to self: you actually can turn the function keys on your Lenovo IdeaPad back into actual function keys.  It's just a BIOS setting.

For anyone else who comes across this, Lenovo's IdeaPad series of laptops (at least the version I have) does weird things with the function keys.  You've probably seen those keyboards where the F1 through F12 keys have alternate functions.  They often have a "function lock" button that's equivalent to caps lock or scroll lock and you can turn it off to access the alternate functionality. 

What Lenovo does is similar, except that the alternate functionality is on by default and there's no function lock button.  There's just an "Fn" button that you have to press and hold to access the normal function button keystrokes.  So on my laptop, F11 and F12 turn the screen brightness up and down and I need to press Fn+F12 to do a regular F12 keystroke.

This is probably great for "regular" users, most of whom wouldn't miss the function keys if they went away completely.  On the other hand, for a developer, this adds a second keystroke to a row of what were previously absurdly convenient hotkeys.  Granted the volume and brightness keys are convenient, but when I'm coding I use F11 and F12 for my IDE keybindings much more often than I need to change the screen brightness.

But luckily, as I found in the link above, you can turn that off in a BIOS setting.

Upgrading Mercurial on shared hosting

Disclaimer: This is yet another "note to self" post.  If you're not me, feel free to ignore it.

After God alone knows how many years (at least six, since I have posts related to it from 2010), it's finally time up upgrade the version of Mercurial that I have installed on my shared web hosting account.  This is a shared hosting account with no shell access - nothing but web-based tools and FTP.  I also don't know what OS it's running - just that it's some form of Linux.  So I've been putting this off for obvious reasons.

Unfortunately for me, the defaults for repository creation in Mercurial 3.7 turn on general delta support by default.  That isn't supported by the old version I was running (1.7), so my choices are to either use the now non-standard, older, and less efficient format my repositories, or just bite the bullet and upgrade.  So I did the latter, since the version I had was pretty ancient and I was going to have to do it eventually anyway.

Fortunately, my hosting provider supports Python 2.7, which gets you most of Mercurial.  However, there are some C-based components to Mercurial.  Since I have no shell access to the hosting server, and there are probably no development tools installed even if I did, I had to try compiling on a VM.  I was able to do that by spinning up a Fedora 24 VM (on the assumption that they're running RHEL, or something close enough to it), and doing a local build.  The only caveat was that apparently my provider is running a 32-bit OS, because building on a 64-bit VM resulted in errors about the ELF format being incorrect.

Once the Fedora VM was up and running, I was able to do a build by running the following:
sudo dnf install python-devel
sudo dnf install redhat-rpm-config
cd /path/to/mercurial-3.8.x
make local

That's about it.  After I had a working build I was able to copy the Mercurial 3.8 folder to the server, right over top of the old version, and it just worked.  Upgrade accomplished!

JSHint regex warnings

Note to self: when getting regular expression warnings from JSHint, remember the inline option for disabling them.

/*jshint regexp: false */

This is useful for adding to the top of a function definition when you want to use "unsafe" regular expressions.  Apparently the idea is that using an unescaped "." in your regular expressions can match more data than you intend and potentially lead to bad validation and insecure applications.  So it's actually a good thing that JSHint check for this.

On the other hand, in my case the regex in question was just a simple extraction of the extension from a file name.  Since I was comparing the result to a white-list and substituting a known default if it wasn't found, there wasn't really any serious risk.  I just wanted JSHint to shut up.

Resize Subsonic frames

I upgraded Subsonic today and I had to remember the little hack I put in to enable resizing of the playlist frame. This is the second time I've had to look up how to do this, so I'm just going to document it here.

By default, each piece of the Subsonic web interface is a frame, and all the frames are borderless and not resizable.  This is kind of a pain because sometimes I have a long playlist and I want to see all of it at once.  The ideal way to do that would be to just increase the size of the playlist frame - but I can't do that.  Fortunately, fixing this is relatively easy.  You can just modify the frameset markup in the following file:
C:\subsonic\jetty\<number>\webapp\WEB-INF\jsp\index.jsp

For my case, the solution was to change the third frameset tag by increasing turning on frame borders and increasing the border size.  Here's the markup:
<frameset rows="75%,25%" border="5" framespacing="0" frameborder="1">

Using RewriteBase without knowing it

So here's an interesting tidbit that I discovered this afternoon: you can use RewriteBase in a .htaccess file without knowing the actual base URL.  This is extremely useful for writing a portable .htaccess file.

In case you don't know, the RewriteBase directive to Apache's mod_rewrite is used to specify the base path used in relative rewrite rules.  Normally, if you don't specify a full path, mod_rewrite will just rewrite the URL relative to the current directory, i.e. the one where the .htaccess file is.  Unfortunately, this isn't always the right thing to do.  For example, if the .htaccess file is under an aliased directory, then mod_rewrite will try to make the URL relative to the filesystem path rather than the path alias, which won't work.

Turns out that you can account for this in four (relatively) simple lines:

RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond $1#%{REQUEST_URI} ([^#]*)#(.*)\1$
RewriteRule ^(.*)$ %2index.php [QSA,L]

All you need to do is substitute in your rewrite target for "index.php" and it "just works".  No changes to server configuration required and no need to edit the RewriteBase for the specific server.

Ext Direct errors

Note to self: when Ext Direct calls start failing, look in the request headers for error messages.  I'm not sure whether it's Ext itself or just our framework, but for whatever reason, Ext Direct calls seem to more or less swallow server-side errors.

In this particular case, I was experimenting with some of the code that our in-house development framework uses to render maps.  We have OpenLayers on the front-end a custom PHP back-end that we communicate with in part through Ext Direct, which is the handy-dandy AJAX RPC framework that comes packaged with Sencha's ExtJS.

So anyway, I made some changes, reloaded the page, and all my Ext Direct calls were failing.  No meaningful error messages, nothing in the JavaScript console, and the response body was just empty.  So what the heck was happening?  (Yeah, I know, I could have just run the unit tests, since we actually have unit tests for the framework code.  But that didn't occur to me because so much of the application code is missing them and I was just experimenting anyway.  Get off my back!) 

Then I noticed, just by chance, that the request headers in the network tab of Chrome's dev tools looked weird.  In particular, it contained this header:
X-Powered-By:PHP/5.3.21 Missing argument 1 for...

So that's what happened to the error message — it got dumped into the headers.  Not terribly helpful, but good to know.